V0.1.4: Implemented github social functions into CatEdit: refactored template and views hierarchy, names, views submodule, created macro to generate category tables + small esthetic changes (buttons to display categories/tables switch state, possibility to hide tables) + views modules have been converted into Flask Blueprints
authorNicolas DURAND <nicolas.durand@iri.centrepompidou.fr>
Fri, 13 Feb 2015 17:22:21 +0100
changeset 42 dac9b1248e0f
parent 41 1fee953e2d9d
child 43 6d0e2523e17d
V0.1.4: Implemented github social functions into CatEdit: refactored template and views hierarchy, names, views submodule, created macro to generate category tables + small esthetic changes (buttons to display categories/tables switch state, possibility to hide tables) + views modules have been converted into Flask Blueprints
src/catedit/__init__.py
src/catedit/persistence.py
src/catedit/resources.py
src/catedit/static/css/style.css
src/catedit/templates/catbase.html
src/catedit/templates/cateditor.html
src/catedit/templates/categories/editor.html
src/catedit/templates/categories/submit.html
src/catedit/templates/categories/workshop.html
src/catedit/templates/catindex.html
src/catedit/templates/catlogin.html
src/catedit/templates/catmodifs.html
src/catedit/templates/catrecap.html
src/catedit/templates/home/index.html
src/catedit/templates/home/login.html
src/catedit/templates/layout.html
src/catedit/templates/macros.html
src/catedit/templates/social/changeset.html
src/catedit/templates/social/comment_thread_layout.html
src/catedit/templates/social/discussion.html
src/catedit/templates/social/index.html
src/catedit/version.py
src/catedit/views.py
src/catedit/views/__init__.py
src/catedit/views/categories.py
src/catedit/views/home.py
src/catedit/views/social.py
src/catedit/views/utils.py
virtualenv/requirements.txt
--- a/src/catedit/__init__.py	Fri Jan 30 12:41:40 2015 +0100
+++ b/src/catedit/__init__.py	Fri Feb 13 17:22:21 2015 +0100
@@ -48,6 +48,16 @@
 )
 github = GitHub(app)
 
+@github.access_token_getter
+def token_getter():
+    """
+        Utility function for github-flask module to get user token when
+        making authenticated requests
+    """
+    if session.get("user_logged", None):
+        # logger.debug("I made an authentified request")
+        return session["user_code"]
+
 # Api
 api = Api(app)
 
@@ -56,10 +66,11 @@
 app.config["CURRENT_VERSION"] = CURRENT_VERSION
 
 # Views and APIs
-from catedit.views import cat_editor, cat_recap, github_login,\
-                          github_callback, logout
-
+from catedit.views import home, categories, social
 from catedit.resources import CategoryAPI, CategoryChangesAPI
+app.register_blueprint(home.module)
+app.register_blueprint(categories.module)
+app.register_blueprint(social.module)
 
 
 api.add_resource(CategoryAPI,
--- a/src/catedit/persistence.py	Fri Jan 30 12:41:40 2015 +0100
+++ b/src/catedit/persistence.py	Fri Feb 13 17:22:21 2015 +0100
@@ -450,14 +450,14 @@
         """
         file_content=""
         try:
-            filedict = github.get("repos/"
-                                  + app.config["PERSISTENCE_CONFIG"]
-                                              ["REPOSITORY_OWNER"] + "/"
-                                  + self.repository
-                                  + "/contents/"
-                                  + app.config["PERSISTENCE_CONFIG"]
-                                              ["CATEGORIES_PATH"]
-                                  + kwargs["name"])
+            filedict = github.get(
+                "repos/"
+                + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+                + self.repository
+                + "/contents/"
+                + app.config["PERSISTENCE_CONFIG"]["CATEGORIES_PATH"]
+                + kwargs["name"]
+            )
             file_content = str(b64decode(filedict["content"]), "utf-8")
         except GitHubError as ghe:
             logger.error("Github Error trying to get file: "+kwargs["name"])
@@ -484,13 +484,13 @@
         """
         filenames_list = []
         try:
-            files_in_repo = github.get("repos/"
-                                       + app.config["PERSISTENCE_CONFIG"]
-                                                   ["REPOSITORY_OWNER"] + "/"
-                                       + self.repository
-                                       + "/contents/"
-                                       + app.config["PERSISTENCE_CONFIG"]
-                                                   ["CATEGORIES_PATH"])
+            files_in_repo = github.get(
+                "repos/"
+                + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+                + self.repository
+                + "/contents/"
+                + app.config["PERSISTENCE_CONFIG"]["CATEGORIES_PATH"] + "?per_page=100"
+            )
             filenames_list = [github_file["name"]
                               for github_file in files_in_repo]
             # logger.debug(filenames_list)
@@ -505,14 +505,14 @@
         file_content_list = []
         for filename in filenames_list:
             try:
-                filedict = github.get("repos/"
-                                      + app.config["PERSISTENCE_CONFIG"]
-                                                  ["REPOSITORY_OWNER"] + "/"
-                                      + self.repository
-                                      + "/contents/"
-                                      + app.config["PERSISTENCE_CONFIG"]
-                                                  ["CATEGORIES_PATH"]
-                                      + filename)
+                filedict = github.get(
+                    "repos/"
+                    + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+                    + self.repository
+                    + "/contents/"
+                    + app.config["PERSISTENCE_CONFIG"]["CATEGORIES_PATH"]
+                    + filename
+                )
                 file_content_list.append(str(b64decode(filedict["content"]),
                                          "utf-8"))
             except GitHubError as ghe:
--- a/src/catedit/resources.py	Fri Jan 30 12:41:40 2015 +0100
+++ b/src/catedit/resources.py	Fri Feb 13 17:22:21 2015 +0100
@@ -45,17 +45,17 @@
         if cat_id is not None:
             cat = cat_manager_instance.load_category(cat_id)
             if cat != None:
-                return cat.cat_graph \
-                          .serialize(format='turtle') \
-                          .decode("utf-8"), \
-                       200
+                return cat.cat_graph.serialize(
+                    format='turtle'
+                ).decode("utf-8"), 200
             else:
                 return 404
         else:
             response = []
             for cat in cat_manager_instance.list_categories():
-                response.append(cat.cat_graph.serialize(format='turtle')
-                                             .decode("utf-8"))
+                response.append(
+                    cat.cat_graph.serialize(format='turtle').decode("utf-8")
+                )
             return response, 200
 
     # update category cat_id
@@ -86,17 +86,26 @@
         args = cat_parser.parse_args()
         if cat_id is None:
             if cat_manager_instance.persistence.session_compliant is True:
-                logger.debug("Submitting - deleted categories are:"
-                             + str(session.get("deleted_categories", {})
-                                          .get(repository, {}))
-                             + " and modified categories are:"
-                             + str(session.get("modified_categories", {})
-                                          .get(repository, {})))
+                logger.debug(
+                    "Submitting - deleted categories are:"
+                    + str(
+                        session.get(
+                            "deleted_categories", {}
+                        ).get(repository, {})
+                    ) + " and modified categories are:"
+                    + str(
+                        session.get(
+                            "modified_categories", {}
+                        ).get(repository, {})
+                    )
+                )
                 cat_manager_instance.save_changes(
-                    deleted_cat_dict=session.get("deleted_categories", {})
-                                            .get(repository, {}),
-                    modified_cat_dict=session.get("modified_categories", {})
-                                             .get(repository, {}),
+                    deleted_cat_dict=session.get(
+                        "deleted_categories", {}
+                    ).get(repository, {}),
+                    modified_cat_dict=session.get(
+                        "modified_categories", {}
+                    ).get(repository, {}),
                     message=args["commit_message"]
                 )
                 session["deleted_categories"]["local"] = {}
@@ -105,13 +114,14 @@
                 return 204
         else:
             # is the edition occuring on an already modified category?
-            if cat_id in session.get("modified_categories", {}) \
-                                .get(repository, {}).keys():
+            if (cat_id in session.get(
+                    "modified_categories", {}
+            ).get(repository, {}).keys()):
                 cat_graph = Graph()
                 cat_graph.parse(
-                    source=StringIO(session["modified_categories"]
-                                           [repository]
-                                           [cat_id]),
+                    source=StringIO(
+                        session["modified_categories"][repository][cat_id]
+                    ),
                     format="turtle"
                 )
                 cat = Category(graph=cat_graph)
@@ -213,18 +223,21 @@
             )
             cat_list = cat_manager_instance.list_categories()
             # first we edit what was already modified before the deletion
-            logger.debug(session.get("modified_categories", {})
-                                .get(repository, {}))
-            if deleted_cat_id in session["deleted_categories"] \
-                                        [repository].keys():
+            logger.debug(
+                session.get("modified_categories", {}).get(repository, {})
+            )
+            if (
+                    deleted_cat_id in
+                    session["deleted_categories"][repository].keys()
+            ):
                 for element in session.get("modified_categories", {}) \
                                       .get(repository, {}).keys():
                     logger.debug(str(element))
                     modified_cat_graph = Graph()
                     modified_cat_graph.parse(
-                        source=StringIO(session["modified_categories"]
-                                               [repository]
-                                               [element]),
+                        source=StringIO(
+                            session["modified_categories"][repository][element]
+                        ),
                         format="turtle"
                     )
                     modified_cat = Category(graph=modified_cat_graph)
@@ -233,12 +246,16 @@
                         new_property_list = []
                         for (predicate, obj) in modified_cat.properties:
                             if not (
-                                app.config["PROPERTY_LIST"]
-                                          [predicate]
-                                          ["object_type"] == "uriref-category"
-                                and
-                                (obj == (app.config["CATEGORY_NAMESPACE"] +
-                                         deleted_cat_id))
+                                    app.config["PROPERTY_LIST"]
+                                    [predicate]
+                                    ["object_type"]
+                                    == "uriref-category"
+                                    and (
+                                        obj == (
+                                            app.config["CATEGORY_NAMESPACE"]
+                                            + deleted_cat_id
+                                        )
+                                    )
                             ):
                                 new_property_list.append((predicate, obj))
 
--- a/src/catedit/static/css/style.css	Fri Jan 30 12:41:40 2015 +0100
+++ b/src/catedit/static/css/style.css	Fri Feb 13 17:22:21 2015 +0100
@@ -1,11 +1,3 @@
-.property-row{
-  margin-top: 4px;
-  margin-bottom: 4px;
-}
-
-.property-delete-button{
-  margin-left: 4px;
-}
 
 .visible{
   display:block;
--- a/src/catedit/templates/catbase.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<html lang="fr">
-<head>
-  {% block head %}
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>{% block title %}{% endblock title %}</title>
-    <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
-    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
-  {% endblock head %}
-</head>
-<body>
-  <div class="navbar navbar-inverse" role="navigation">
-    <div class="container">
-      <div class="navbar-header">
-        <a class="navbar-brand" href="{{ url_for('cat_index') }}">
-          <img alt="Brand" src="{{ url_for('static', filename='img/catedit_brand.png') }}" class="navbar-img" width="32" height="32">
-        </a>
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
-          <span class="sr-only">Toggle navigation</span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </button>
-      </div>
-      <div class="collapse navbar-collapse">
-        <ul class="nav navbar-nav">
-          {% block navbar_items %}
-            <li><a href="{{ url_for('cat_index') }}">Page d'accueil</a></li>
-          {% endblock navbar_items %}
-        </ul>
-        <div class="navbar-text navbar-right">
-          {% if not session.get("user_logged", None)%} Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
-          {% else %} Authentifié: {{ session["user_login"] }} - <a href="{{ url_for('logout') }}" class="navbar-link">Quitter</a>{% endif %}
-        </div>
-        {% if session["user_logged"] %}
-          <form class="navbar-form navbar-right">
-            <select class="form-control select-repo" name="navrepo" onchange="window.location.href=this.form.navrepo.options[this.form.navrepo.selectedIndex].value">
-              {% block repo_list %}
-                {% for repo in session.get("user_repositories", []) %}
-                  <option value="{{url_for('cat_recap', repository=repo)}}" {% if repo==current_repository %}selected="selected"{% endif %}>{{repo}}</option>
-                {% endfor %}
-              {% endblock repo_list %}
-            </select>
-          </form>
-        {% endif %}
-      </div>
-    </div>
-  </div>
-  <div class="container">
-    {% block page_content %}
-    {% endblock page_content %}
-  </div>
-  <footer class="footer">
-    <div class="text-right footer-notes">
-      CatEdit - Prototype v{{config["CURRENT_VERSION"]}}
-      <br><br>
-    </div>
-  </footer>
-</body>
--- a/src/catedit/templates/cateditor.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-{% extends "catbase.html" %}
-{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
-  {% set readonly="readonly" %}
-{% else %}
-  {% set readonly=False %}
-{% endif %}
-{% block title %} {{ current_repository}}: Editeur {% endblock title %}
-{% block head %}
-  {{ super() }}
-{% endblock head %}
-{% block navbar_items %}
-  {{ super() }}
-  {% if session.get("user_logged", None) %}
-    <li><a class="navbar-decorative">></a></li>
-    <li><a href="{{ url_for('cat_recap', repository=current_repository) }}">Atelier</a></li>
-    <li><a class="navbar-decorative">></a></li>
-    <li class="active"><a>Editeur</a></li>
-  {% endif %}
-{% endblock navbar_items%}
-{% block repo_list %}
-  {{ super() }}
-{% endblock repo_list %}
-{% block page_content %}
-<h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
-{% if session["user_logged"] and not session["user_can_edit"][current_repository] %}
-<div class="alert alert-warning" role="alert">
-  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-  <span class="sr-only">Attention:</span>
-  Vous n'avez pas accès en écriture au repository contenant les catégories - Vous ne pourrez pas les modifier.
-</div>
-{% endif %}
-{% if not session["user_logged"] %}
-<div class="alert alert-warning" role="alert">
-  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-  <span class="sr-only">Attention:</span>
-  Vous devez être authentifié pour modifier les catégories.
-</div> {% endif %}
-{% if form.label.errors or form.description.errors %}
-<div class="alert alert-danger">
-  <strong>
-    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-    Erreur:
-  </strong>
-  Vous n'avez pas rempli certains champs obligatoires.
-</div>
-{% endif %}
-  <h3>{% if cat_id: %} Edition : <small>Catégorie existante</small>{% else %}Création : <small>Nouvelle catégorie</small>{% endif %}</h3>
-  {% if readonly %} <fieldset disabled> {% endif %}
-  <form method="POST" action="{% if cat_id %}{{ url_for('cat_editor', cat_id=cat_id, repository=current_repository) }}{% else %}{{ url_for('cat_editor', repository=current_repository) }}{% endif %}" id="cat_form" role="form">
-    {{ form.hidden_tag() }}
-    {% if form.label.errors %}
-      {% set label_placeholder="Champ obligatoire" %}
-    {% endif %}
-    {% if form.description.errors %}
-      {% set description_placeholder="Champ obligatoire" %}
-    {% endif %}
-    {{ form.label.label }} <br> {{ form.label(size=40, class="form-control", readonly=readonly, placeholder=label_placeholder) }} <br>
-    {{ form.description.label }} <br> {{ form.description(size=150, class="form-control", readonly=readonly, placeholder=description_placeholder) }} <br>
-    <label>Propriétés </label>
-    <div class="form-inline">
-      <select id="property_selector" class="form-control" onChange="CatEditScripts.displayCorrespondingField();" {{readonly}}>
-        <option label="property_type_default" selected="selected">
-          Liste des propriétés ...
-        </option>
-        {% for predicate in config["PROPERTY_LIST"] %}
-        <option value='{{ predicate }}' label={{ config["PROPERTY_LIST"][predicate]["object_type"] }} >{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</option>
-        {% endfor %}
-      </select>
-      <input type="text" id="literal-field" class="hidden form-control">
-      <input type="text" id="uriref-link-field" placeholder="http://my-example.com" class="hidden form-control">
-      <select class="hidden form-control" id="uriref-category-field">
-        <option value="default" selected="selected"> Liste des catégories </option>
-        {% for cat in cat_list %}
-          <option value="{{ cat.cat_id }}"> {{ cat.cat_label }} </option>
-        {% endfor %}
-      </select>
-      <input type="button" value="Ajouter propriété" onClick="CatEditScripts.addProperty('properties_div','property_table_body','{{ csrf_token() }}');" class="btn btn-default" {{ readonly }}>
-    </div>
-    <div class="alert alert-warning hidden" role="alert" id="uriref-category-field-text">
-    </div>
-    <div id="properties_div">
-      <div class="row">
-        <div class="col-md-6">
-          <br>
-          <table id="property_table" class="table table-condensed">
-            <tbody id="property_table_body">
-            {% set property_count=0 %}
-            {% for property in form.properties %}
-                <tr id="properties-{{property_count}}">
-                  <input id="properties-{{property_count}}-csrf_token" name="properties-{{property_count}}-csrf_token" value="{{ csrf_token() }}" type="hidden">
-                  {% if config["PROPERTY_LIST"][property.property_predicate.data]["object_type"]=="uriref-category" %}
-                    {% for cat in cat_list %}
-                      {% if property.property_object.data == cat.cat_id %}
-                        {% if cat.cat_id not in deleted_cat_list %}
-                          {{ property.property_predicate() }}
-                          {{ property.property_object() }}
-                          <td id="predicate_td{{ property_count }}">
-                            <strong>{{ config["PROPERTY_LIST"][property.property_predicate.data]["descriptive_label_fr"] }}</strong>
-                          </td>
-                          <td id="object_td{{property_count-1}}">
-                            {{ cat.cat_label }}
-                          </td>
-                          <td id="delete_button_td{{property_count-1}}" class="text-center">
-                            <input type="button" id="property_delete_button{{property_count-1}}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties_div')" value="Supprimer">
-                          </td>
-                        {% endif %}
-                      {% endif %}
-                    {% endfor %}
-                  {% else %}
-                      {{ property.property_predicate() }}
-                      {{ property.property_object() }}
-                    <td id="predicate_td{{ property_count }}">
-                      <strong>{{ config["PROPERTY_LIST"][property.property_predicate.data]["descriptive_label_fr"] }}</strong>
-                    </td>
-                    <td id="object_td{{ property_count }}">
-                      {% if config["PROPERTY_LIST"][property.property_predicate.data]["object_type"]=="uriref-link" %}
-                        <a href="{{ property.property_object.data }}">{{ property.property_object.data }}</a>
-                      {% else %}
-                        {{ property.property_object.data }}
-                      {% endif %}
-                    </td>
-                    <td id="delete_button_td{{property_count-1}}" class="text-center">
-                      <input type="button" id="property_delete_button{{property_count-1}}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties_div')" value="Supprimer">
-                    </td>
-                  {% endif %}
-                {% set property_count=property_count+1 %}
-                </tr>
-            {% endfor %}
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div><br>
-    <input type="submit" value="Sauvegarder" class="btn btn-default">
-    <a href="{{ url_for('cat_recap', repository=current_repository)}}"class="btn btn-default">Annuler</a>
-  </form>
-  {% if readonly %} </fieldset> {% endif %}
-  <script src="{{ url_for('static', filename='js/property_functions.js') }}" language="Javascript" type="text/javascript"></script>
-  {% if cat_id %}
-    <script type=text/javascript>
-      CatEditScripts.initPropertyCount('properties_div',{{cat_properties|length}});
-      CatEditScripts.initCreatedProperties({{cat_properties|length}});
-    </script>
-  {% endif %}
-{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/categories/editor.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,146 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %} {{ current_repository}}: Editeur {% endblock title %}
+{% block head %}
+  {{ super() }}
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  {% if session.get("user_logged", None) %}
+    <li><a class="navbar-decorative">></a></li>
+    <li><a href="{{ url_for('categories.workshop', repository=current_repository) }}">Atelier</a></li>
+    <li><a class="navbar-decorative">></a></li>
+    <li class="active"><a>Editeur</a></li>
+  {% endif %}
+{% endblock navbar_items%}
+{% block repo_list %}
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+<h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+<div class="alert alert-warning" role="alert">
+  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+  <span class="sr-only">Attention:</span>
+  Vous n'avez pas accès en écriture au repository contenant les catégories - Vous ne pourrez pas les modifier.
+</div>
+{% endif %}
+{% if not session["user_logged"] %}
+<div class="alert alert-warning" role="alert">
+  <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+  <span class="sr-only">Attention:</span>
+  Vous devez être authentifié pour modifier les catégories.
+</div> {% endif %}
+{% if form.label.errors or form.description.errors %}
+<div class="alert alert-danger">
+  <strong>
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    Erreur:
+  </strong>
+  Vous n'avez pas rempli certains champs obligatoires.
+</div>
+
+{% endif %}
+  <h3>{% if cat_id: %} Edition : <small>Catégorie existante</small>{% else %}Création : <small>Nouvelle catégorie</small>{% endif %}</h3>
+  {% if readonly %} <fieldset disabled> {% endif %}
+  <form method="POST" action="{% if cat_id %}{{ url_for('categories.editor', cat_id=cat_id, repository=current_repository) }}{% else %}{{ url_for('categories.editor', repository=current_repository) }}{% endif %}" id="cat_form" role="form">
+    {{ form.hidden_tag() }}
+    {% if form.label.errors %}
+      {% set label_placeholder="Champ obligatoire" %}
+    {% endif %}
+    {% if form.description.errors %}
+      {% set description_placeholder="Champ obligatoire" %}
+    {% endif %}
+    {{ form.label.label }} <br> {{ form.label(size=40, class="form-control", readonly=readonly, placeholder=label_placeholder) }} <br>
+    {{ form.description.label }} <br> {{ form.description(size=150, class="form-control", readonly=readonly, placeholder=description_placeholder) }} <br>
+    <label>Propriétés </label>
+    <div class="form-inline">
+      <select id="property_selector" class="form-control" onChange="CatEditScripts.displayCorrespondingField();" {{readonly}}>
+        <option label="property_type_default" selected="selected">
+          Liste des propriétés ...
+        </option>
+        {% for predicate in config["PROPERTY_LIST"] %}
+        <option value='{{ predicate }}' label={{ config["PROPERTY_LIST"][predicate]["object_type"] }} >{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</option>
+        {% endfor %}
+      </select>
+      <input type="text" id="literal-field" class="hidden form-control">
+      <input type="text" id="uriref-link-field" placeholder="http://my-example.com" class="hidden form-control">
+      <select class="hidden form-control" id="uriref-category-field">
+        <option value="default" selected="selected"> Liste des catégories </option>
+        {% for cat in cat_list %}
+          <option value="{{ cat.cat_id }}"> {{ cat.cat_label }} </option>
+        {% endfor %}
+      </select>
+      <input type="button" value="Ajouter propriété" onClick="CatEditScripts.addProperty('properties_div','property_table_body','{{ csrf_token() }}');" class="btn btn-default" {{ readonly }}>
+    </div>
+    <div class="alert alert-warning hidden" role="alert" id="uriref-category-field-text">
+    </div>
+    <div id="properties_div">
+      <div class="row">
+        <div class="col-md-6">
+          <br>
+          <table id="property_table" class="table table-condensed">
+            <tbody id="property_table_body">
+            {% set property_count=0 %}
+            {% for property in form.properties %}
+                <tr id="properties-{{property_count}}">
+                  <input id="properties-{{property_count}}-csrf_token" name="properties-{{property_count}}-csrf_token" value="{{ csrf_token() }}" type="hidden">
+                  {% if config["PROPERTY_LIST"][property.property_predicate.data]["object_type"]=="uriref-category" %}
+                    {% for cat in cat_list %}
+                      {% if property.property_object.data == cat.cat_id %}
+                        {% if cat.cat_id not in deleted_cat_list %}
+                          {{ property.property_predicate() }}
+                          {{ property.property_object() }}
+                          <td id="predicate_td{{ property_count }}">
+                            <strong>{{ config["PROPERTY_LIST"][property.property_predicate.data]["descriptive_label_fr"] }}</strong>
+                          </td>
+                          <td id="object_td{{property_count-1}}">
+                            {{ cat.cat_label }}
+                          </td>
+                          <td id="delete_button_td{{property_count-1}}" class="text-center">
+                            <input type="button" id="property_delete_button{{property_count-1}}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties_div')" value="Supprimer">
+                          </td>
+                        {% endif %}
+                      {% endif %}
+                    {% endfor %}
+                  {% else %}
+                      {{ property.property_predicate() }}
+                      {{ property.property_object() }}
+                    <td id="predicate_td{{ property_count }}">
+                      <strong>{{ config["PROPERTY_LIST"][property.property_predicate.data]["descriptive_label_fr"] }}</strong>
+                    </td>
+                    <td id="object_td{{ property_count }}">
+                      {% if config["PROPERTY_LIST"][property.property_predicate.data]["object_type"]=="uriref-link" %}
+                        <a href="{{ property.property_object.data }}">{{ property.property_object.data }}</a>
+                      {% else %}
+                        {{ property.property_object.data }}
+                      {% endif %}
+                    </td>
+                    <td id="delete_button_td{{property_count-1}}" class="text-center">
+                      <input type="button" id="property_delete_button{{property_count-1}}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties_div')" value="Supprimer">
+                    </td>
+                  {% endif %}
+                {% set property_count=property_count+1 %}
+                </tr>
+            {% endfor %}
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div><br>
+    <input type="submit" value="Sauvegarder" class="btn btn-default">
+    <a href="{{ url_for('categories.workshop', repository=current_repository)}}"class="btn btn-default">Annuler</a>
+  </form>
+  {% if readonly %} </fieldset> {% endif %}
+  <script src="{{ url_for('static', filename='js/property_functions.js') }}" language="Javascript" type="text/javascript"></script>
+  {% if cat_id %}
+    <script type=text/javascript>
+      CatEditScripts.initPropertyCount('properties_div',{{cat_properties|length}});
+      CatEditScripts.initCreatedProperties({{cat_properties|length}});
+    </script>
+  {% endif %}
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/categories/submit.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,156 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %}{{current_repository}}: Soumission{% endblock title %}
+{% block head %}
+  {{ super() }}
+  <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
+  <script>
+    $(document).ready(function(){
+      $(".cat-delete-div").hide();
+      $(".cat-info-div").hide();
+      $(".cat-table-toggle").click(function(evt){
+        $(".cat-table").slideToggle(function(){
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-up");
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-down");
+        });
+      });
+      $(".cat-delete-toggle").click(function(evt){
+        $("#delete_confirm_"+evt.target.id.split('_').slice(2,5).join('_')).slideToggle("fast");
+      });
+      $(".cat-info-toggle").click(function(evt){
+        $("#properties_"+evt.target.id.split('_').slice(2,5).join('_')).slideToggle(function(){
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-plus-sign");
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-minus-sign");
+        });
+      });
+    });
+  </script>
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  {% if session.get("user_logged", None) %}
+  <li><a class="navbar-decorative">></a></li>
+  <li><a href="{{ url_for('categories.workshop', repository=current_repository) }}">Atelier</a></li>
+  <li><a class="navbar-decorative">></a></li>
+  <li class="active"><a>Soumission</a></li>
+  {% endif %}
+{% endblock navbar_items %}
+{% block repo_list %}
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+  <h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+  <h3> Catégories existantes <a title="Afficher/Cacher tableau" class="btn btn-default cat-table-toggle">
+    <span class="glyphicon glyphicon-chevron-up"/>
+  </a></h3>
+  <div class="cat-table">
+    <table class="table table-bordered table-condensed">
+      <thead>
+        <tr class="active">
+          <th class="col-md-2"><b>Nom de la catégorie</b></th>
+          <th class="col-md-10" colspan="5"><b>Description de la catégorie</b></th>
+        </tr>
+      </thead>
+      <tbody>
+      {% if not session["user_logged"] %}
+      <tr>
+        <td class="col-md-12" colspan="5">
+          <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+          <span class="sr-only">Attention:</span>
+          Veuillez vous identifier pour visualiser les catégories
+        </td>
+      </tr>
+      {% else %}
+        {% if original_cat_count == 0 %}
+          <tr>
+            <td class="col-md-12" colspan="3">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('categories.editor', repository=current_repository) }}">Créer une catégorie</a>{% endif %}</td>
+          </tr>
+        {% else %}
+          {% import "macros.html" as macros %}
+          {{ macros.category_table(original_cat_list, current_repository, state_list=["original"], target="categories.submit") }}
+        {% endif %}
+      {% endif %}
+      </tbody>
+    </table>
+  </div>
+
+  <h3> Mes modifications </h3>
+  <table class="table table-bordered table-condensed">
+    <thead>
+      <tr class="active">
+        <th class="col-md-2"><b>Nom de la catégorie</b></th>
+        <th class="col-md-10" colspan="4"><b>Description de la catégorie</b></th>
+      </tr>
+    </thead>
+    <tbody>
+    {% if not session["user_logged"] %}
+    <tr>
+      <td class="col-md-12" colspan="5">
+        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+        <span class="sr-only">Attention:</span>
+        Veuillez vous identifier pour visualiser les modifications
+      </td>
+    </tr>
+    {% else %}
+      <tr class="success">
+        <td class="col-md-12" colspan="5">
+          <b> Catégories ajoutées</b>
+        </td>
+      </tr>
+      {% if created_cat_count == 0 %}
+        <tr>
+          <td class="col-md-12" colspan="5">Aucune catégorie n'a été ajoutée pour l'instant.</td>
+        </tr>
+      {% else %}
+        {% import "macros.html" as macros %}
+        {{ macros.category_table(cat_list, current_repository, state_list=["created"], target="categories.submit") }}
+      {% endif %}
+      <tr class="warning">
+        <td class="col-md-12" colspan="5">
+          <b> Catégories modifiées</b>
+        </td>
+      </tr>
+      {% if edited_cat_count == 0 %}
+      <tr>
+        <td class="col-md-12" colspan="5">Aucune catégorie n'a été modifiée pour l'instant.</td>
+      </tr>
+      {% else %}
+        {% import "macros.html" as macros %}
+        {{ macros.category_table(cat_list, current_repository, state_list=["modified"], target="categories.submit") }}
+      {% endif %}
+
+      <tr class="danger">
+        <td class="col-md-12" colspan="5">
+          <b> Catégories supprimées</b>
+        </td>
+      </tr>
+      {% if deleted_cat_count == 0 %}
+      <tr>
+        <td class="col-md-12" colspan="5">Aucune catégorie n'a été supprimée pour l'instant.</td>
+      </tr>
+      {% else %}
+        {% import "macros.html" as macros %}
+        {{ macros.category_table(cat_list, current_repository, state_list=["deleted"], target="categories.submit") }}
+      {% endif %}
+    {% endif %}
+    </tbody>
+  </table>
+  <h3> Soumettre mes changements </h3>
+  <div class="col-md-12">
+    <form method="POST" action="{{ url_for('categories.submit', repository=current_repository)}}">
+      <fieldset {% if readonly %}disabled{% endif %}>
+        {{ commit_form.hidden_tag() }}
+        <div class="form-group">
+          {{ commit_form.commit_message.label }}
+          {{ commit_form.commit_message(size=40, class="form-control", readonly=readonly) }}
+        </div>
+        <button type="submit" class="btn btn-default">Soumettre modifications</button>
+        <a href="{{ url_for('categories.workshop', repository=current_repository)}}"class="btn btn-default">Retour</a>
+      </fieldset>
+    </form><br>
+  </div>
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/categories/workshop.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,101 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %}{{current_repository}}: Atelier{% endblock title %}
+{% block head %}
+  {{ super() }}
+  <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
+  <script>
+    $(document).ready(function(){
+      $(".cat-delete-div").hide();
+      $(".cat-info-div").hide();
+      $(".cat-table-toggle").click(function(evt){
+        $(".cat-table").slideToggle(function(){
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-up");
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-down");
+        });
+      });
+      $(".cat-delete-toggle").click(function(evt){
+        $("#delete_confirm_"+evt.target.id.split('_').slice(2,5).join('_')).slideToggle();
+      });
+      $(".cat-info-toggle").click(function(evt){
+        $("#properties_"+evt.target.id.split('_').slice(2,5).join('_')).slideToggle(function(){
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-plus-sign");
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-minus-sign");
+        });
+      });
+    });
+  </script>
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  {% if session.get("user_logged", None) %}
+    <li><a class="navbar-decorative">></a></li>
+    <li class="active"><a>Atelier</a></li>
+  {% endif %}
+{% endblock navbar_items %}
+{% block repo_list %}
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+  <h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+  <h3> Créer une catégorie : <a href="{{url_for('categories.editor', repository=current_repository)}}" title="Créer catégorie" class="btn btn-default {% if readonly %}disabled{% endif %}"><span class="glyphicon glyphicon-plus"/></a></h3>
+  {% if session["user_logged"] and not session["user_can_edit"] %}
+  <div class="alert alert-warning" role="alert">
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    <span class="sr-only">Attention:</span>
+    Vous n'avez pas accès en écriture au repository contenant les catégories - Vous ne pourrez pas les modifier.
+  </div>
+  {% endif %}
+  <h3>Mes catégories <a title="Afficher/Cacher tableau" class="btn btn-default cat-table-toggle">
+    <span class="glyphicon glyphicon-chevron-up"/>
+  </a></h3>
+  <div class="cat-table">
+    <table class="table table-bordered table-condensed">
+      <thead>
+        <tr class="active">
+          <th class="col-md-2"><b>Nom de la catégorie</b></th>
+          <th class="col-md-10"><b>Description de la catégorie</b></th>
+        </tr>
+      </thead>
+      <tbody>
+      {% if not session["user_logged"] %}
+      <tr>
+        <td class="col-md-12" colspan="2">
+          <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+          <span class="sr-only">Attention:</span>
+          Veuillez vous identifier pour visualiser les catégories
+        </td>
+      </tr>
+      {% else %}
+        {% if cat_list|length == 0 %}
+          <tr>
+            <td class="col-md-12" colspan="2">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('categories.editor', repository=current_repository) }}">Créer une catégorie</a>{% endif %}</td>
+          </tr>
+        {% else %}
+          {% import "macros.html" as macros %}
+          {{ macros.category_table(cat_list, current_repository, target="categories.workshop") }}
+        {% endif %}
+      {% endif %}
+      </tbody>
+    </table>
+  </div>
+  {% if session.get("user_logged") %}
+  <form method="POST" action="{{url_for('categories.workshop', repository=current_repository)}}" class="form-inline">
+    <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
+    <h4> Annuler tous mes changements actuels :
+      <a title="Supprimer changements">
+        <button type="submit" class="btn btn-default" {% if readonly %}disabled{% endif %}>
+          <span class="glyphicon glyphicon-remove"/>
+        </button>
+      </a>
+    </h4>
+  </form>
+  <h4> Soumettre mes changements actuels : <a href="{{ url_for('categories.submit', repository=current_repository)}}" title="Soumettre changements" class="btn btn-default" {% if readonly %}disabled{% endif %}><span class="glyphicon glyphicon-share"/></a>
+  </h4>
+  {% endif %}
+  <h3> Discussions sur cet ensemble de catégories : <a href="{{ url_for('social.index', repository=current_repository)}}" title="Aller sur la liste des discussions" class="btn btn-default"><span class="glyphicon glyphicon-comment"/></a></h3>
+{% endblock page_content %}
--- a/src/catedit/templates/catindex.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-{% extends "catbase.html" %}
-{% if not session["user_logged"] %}
-  {% set readonly="readonly" %}
-{% else %}
-  {% set readonly=False %}
-{% endif %}
-{% block title %}Page d'accueil{% endblock title %}
-{% block head %}
-  {{ super() }}
-{% endblock head %}
-{% block navbar_items %}
-  <li class="active"><a>Page d'accueil</a></li>
-{% endblock navbar_items %}
-{% block repo_list %}
-  <option value="" selected="selected"> Sélectionnez ensemble... </option>
-  {{ super() }}
-{% endblock repo_list %}
-{% block page_content %}
-  <h2> <b>CatEdit</b>: Editeur de Catégories</h2>
-  <h3> Mode d'emploi </h3>
-  <div class="col-md-8">
-    <p>
-      CatEdit est un outil permettant de créer et d'éditer des catégories.
-      Vous aurez besoin d'un compte Github pour utiliser CatEdit:
-      <a href="https://github.com/">s'inscrire sur Github</a>
-    </p>
-    <p>
-      Une fois authentifié, choisissez un ensemble de catégorie dans la liste se trouvant
-      dans la barre de navigation pour être redirigé vers l'atelier correspondant.
-      Vous pourrez y trouver la liste des catégories existantes pour cet ensemble,
-      présentée dans un tableau qui initialement ne comprend que les catégories de l'état courant.
-    </p>
-    <p>
-      L'état courant de l'ensemble de catégorie est la base à partir de laquelle
-      vous pourrez créer et éditer des catégories. Une catégorie consiste en un label, unique
-      et non-vide, une description, unique et non-vide et un nombre variable de propriétés.
-    </p>
-    <h4><b>La liste de catégories</b></h4>
-    <p>
-      A chaque fois que vous faites des modifications, elles sont stockées
-      de manière temporaire tant que vous restez authentifié. La liste de catégories
-      dans l'Atelier est mise à jour au fil de vos modifications selon un code couleur
-      vous permettant de visualiser vos changements.
-    </p>
-      <ul>
-        <li>
-          Une ligne de tableau <b>blanche</b> représente une catégorie de l'état courant
-          que vous n'avez ni modifié ni supprimé.
-        </li>
-        <li>
-          Une ligne de tableau <b class="text-success">verte</b> représente une catégorie que vous avez créée de zéro
-          et qui n'existait pas précédemment dans l'état courant.
-        </li>
-        <li>
-          Une ligne de tableau <b class="text-warning">orange</b> représente une catégorie qui existait
-          initialement et que vous avez modifié.
-        </li>
-        <li>
-          Une ligne de tableau <b class="text-danger">rouge</b> représente une catégorie de l'état courant que
-          vous avez supprimé.
-        </li>
-      </ul>
-    </p>
-    <p>
-      Pour que vos modifications soient permanentes et qu'elles deviennent le nouvel état courant
-      de l'ensemble de catégories, vous devez soumettre vos modifications en suivant le lien dans l'Atelier.
-      Vous trouverez une page vous présentant l'état initial d'une part et vos changements d'autre part.
-    </p>
-    <p>
-      Vous devez obligatoirement renseigner une explication de vos changements avant soumission.
-    </p>
-    <h3> Interface de discussion </h3>
-    <p>
-      Chaque soumission de modifications ouvrira sur GitHub un fil de discussion ayant pour titre le message
-      de soumission. Vous pouvez accéder à la liste de changements via un lien dans l'Atelier. Cela vous
-      redirigera vers la page GitHub présentant tous les changements, et vous pouvez accéder à la discussion
-      d'un changement donné en cliquant sur son titre.
-    </p>
-    <p>
-      Il est aussi possible de créer des discussions générales sur un ensemble de catégories. Un lien est disponible
-      dans l'atelier vers la page "Issues" de l'ensemble de catégories sur GitHub, où vous pouvez commenter et créer
-      de nouvelles "Issues" pour alimenter la discussion sur l'ensemble de catégories considéré.
-    </p>
-    <br><br>
-  </div>
-{% endblock page_content %}
--- a/src/catedit/templates/catlogin.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-{% extends "catbase.html" %}
-{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
-  {% set readonly="readonly" %}
-{% else %}
-  {% set readonly=False %}
-{% endif %}
-{% block title %} Catedit: Login {% endblock title %}
-{% block head %}
-  {{ super() }}
-{% endblock head %}
-{% block navbar_items %}
-  {{ super() }}
-{% endblock navbar_items%}
-{% block repo_list %}
-  {{ super() }}
-{% endblock repo_list %}
-{% block page_content %}
-<h2> <b>CatEdit</b> - <small>Authentification</small></h2>
-{% if form.user_login.errors or form.user_password.errors %}
-<div class="alert alert-danger">
-  <strong>
-    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-    Erreur:
-  </strong>
-  Vous n'avez pas rempli certains champs obligatoires.
-</div>
-{% endif %}
-<div class="col-md-8">
-  <p>
-    <br>
-    Veuillez entrez votre nom d'utilisateur Github et mot de passe Github.
-  </p>
-  <form method="POST" action="{{url_for('github_login')}}" id="login_form" role="form">
-    <div class="form-group">
-      {% if form.user_login.errors %}
-        {% set login_placeholder="Champ obligatoire" %}
-      {% endif %}
-      {{ form.hidden_tag() }}
-      {{form.user_login.label}}
-      {{form.user_login(class="form-control", id="user_login", placeholder=login_placeholder)}}
-      {{form.user_password.label}}
-      {{form.user_password(class="form-control", id="user_password")}}
-    </div>
-    <button type="submit" class="btn btn-default">Me connecter à CatEdit</button>
-  </form>
-{% if form.user_login.data and not(form.user_login.errors or form.user_password.errors) %}
-  <br>
-  <div class="col-md-8 alert alert-info">
-    <p>
-      Il semble que vous utilisez CatEdit pour la première fois. Veuillez cliquer
-      sur le lien suivant pour vous authentifier sur Github afin de pouvoir utiliser CatEdit.
-    </p>
-    <p>
-      Si ça n'est pas la première fois que vous utilisez CatEdit, vérifiez que vous n'avez pas entré
-      un mauvais nom d'utilisateur/mot de passe. Note: Si vous souhaitez changer d'utilisateur,
-      n'oubliez pas auparavant de vous déconnecter de l'utilisateur courant sur <a href="http://github.com">Github</a>.<br><br>
-    </p>
-    <form method="POST" action="{{url_for('github_login_confirm')}}" id="confirm_form" role="form">
-      <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-      <button type="submit" class="btn btn-default">M'authentifier sur Github</button>
-    </form>
-  </div>
-{% endif %}
-</div>
-{% endblock page_content%}
--- a/src/catedit/templates/catmodifs.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,329 +0,0 @@
-{% extends "catbase.html" %}
-{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
-  {% set readonly="readonly" %}
-{% else %}
-  {% set readonly=False %}
-{% endif %}
-{% block title %}{{current_repository}}: Soumission{% endblock title %}
-{% block head %}
-  {{ super() }}
-  <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
-  <script>
-    $(document).ready(function(){
-      {% for cat in existing_cat_list %}
-        $("#properties_{{cat.cat_id}}").hide();
-        $("#info_button_{{cat.cat_id}}").click(function(){
-          $("#properties_{{cat.cat_id}}").slideToggle();
-        });
-      {% endfor %}
-      {% for cat in modified_cat_list %}
-        $("#properties_modified_{{cat.cat_id}}").hide();
-        $("#info_button_modified_{{cat.cat_id}}").click(function(){
-          $("#properties_modified_{{cat.cat_id}}").slideToggle();
-        });
-        $("#delete_modified_{{cat.cat_id}}").hide();
-        $("#remove_modifs_modified_{{cat.cat_id}}").click(function(){
-          $("#delete_modified_{{cat.cat_id}}").slideToggle();
-        });
-      {% endfor %}
-      {% for cat in created_cat_list %}
-        $("#properties_created_{{cat.cat_id}}").hide();
-        $("#info_button_created_{{cat.cat_id}}").click(function(){
-          $("#properties_created_{{cat.cat_id}}").slideToggle();
-        });
-        $("#delete_created_{{cat.cat_id}}").hide();
-        $("#remove_modifs_created_{{cat.cat_id}}").click(function(){
-          $("#delete_created_{{cat.cat_id}}").slideToggle();
-        });
-      {% endfor %}
-    });
-  </script>
-{% endblock head %}
-{% block navbar_items %}
-  {{ super() }}
-  {% if session.get("user_logged", None) %}
-  <li><a class="navbar-decorative">></a></li>
-  <li><a href="{{ url_for('cat_recap', repository=current_repository) }}">Atelier</a></li>
-  <li><a class="navbar-decorative">></a></li>
-  <li class="active"><a>Soumission</a></li>
-  {% endif %}
-{% endblock navbar_items %}
-{% block repo_list %}
-  {{ super() }}
-{% endblock repo_list %}
-{% block page_content %}
-  <h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
-  <h3> Catégories existantes </h3>
-  <table class="table table-bordered table-condensed">
-    <thead>
-      <tr class="active">
-        <th class="col-md-2"><b>Nom de la catégorie</b></th>
-        <th class="col-md-10"><b>Description de la catégorie</b></th>
-      </tr>
-    </thead>
-    <tbody>
-    {% if not session["user_logged"] %}
-    <tr>
-      <td class="col-md-12" colspan="2">
-        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-        <span class="sr-only">Attention:</span>
-        Veuillez vous identifier pour visualiser les catégories
-      </td>
-    </tr>
-    {% else %}
-      {% if existing_cat_list|length == 0 %}
-        <tr>
-          <td class="col-md-12" colspan="2">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('cat_editor', repository=current_repository) }}">Créer une catégorie</a>{% endif %}</td>
-        </tr>
-      {% else %}
-        {% for cat in existing_cat_list %}
-          <tr>
-            <td class="col-md-2">{{ cat.cat_label }}</td>
-            <td class="col-md-8">{{ cat.cat_description}}</td>
-              <td class="text-center">
-                  <a title="Détails catégorie"><button class="btn btn-default" id="info_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon glyphicon-plus-sign"/></button></a>
-              </td>
-              {% if cat.cat_id not in deleted_cat_namelist and cat.cat_id not in modified_cat_namelist %}
-              <td class="text-center">
-                <a href="{{ url_for('cat_editor', cat_id=cat.cat_id, repository=current_repository)}}" title="Editer catégorie" class="btn btn-default"><span class="glyphicon glyphicon glyphicon-pencil"/></a>
-              </td>
-              <td class="text-center">
-                <a title="Supprimer catégorie">
-                  <form method="POST" action="{{ url_for('cat_modifs', deleted_cat_id=cat.cat_id, repository=current_repository) }}" class="form-inline form-button">
-                    <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                    <fieldset {% if readonly %}disabled{% endif %}>
-                      <button type="submit" class="btn btn-default">
-                        <span class="glyphicon glyphicon-trash" title="Supprimer catégorie"/>
-                      </button>
-                    </fieldset>
-                  </form>
-                </a>
-              </td>
-              {% else %}
-              <td colspan="2">
-              </td>
-              {% endif %}
-          </tr>
-            <tr>
-              <td colspan="2">
-                <div id="properties_{{cat.cat_id}}">
-                  <dl class="dl-horizontal">
-                  {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
-                  {% else %}
-                    {% for (predicate, object) in cat.cat_properties %}
-                      <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
-                      <dd>
-                        {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
-                          {% for cat in existing_cat_list %}
-                            {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
-                              {{ cat.cat_label }}
-                            {% endif %}
-                          {% endfor %}
-                        {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
-                          <a href="{{ object }}">{{ object }}</a>
-                        {% else %}
-                          {{ object }}
-                        {% endif %}
-                      </dd>
-                    {% endfor %}
-                  {% endif %}
-                  </dl>
-                </div>
-              </td>
-            </tr>
-        {% endfor %}
-      {% endif %}
-    {% endif %}
-    </tbody>
-  </table>
-
-  <h3> Mes modifications </h3>
-  <table class="table table-bordered table-condensed">
-    <thead>
-      <tr class="active">
-        <th class="col-md-2"><b>Nom de la catégorie</b></th>
-        <th class="col-md-10" colspan="2"><b>Description de la catégorie</b></th>
-      </tr>
-    </thead>
-    <tbody>
-    {% if not session["user_logged"] %}
-    <tr>
-      <td class="col-md-12" colspan="3">
-        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-        <span class="sr-only">Attention:</span>
-        Veuillez vous identifier pour visualiser les modifications
-      </td>
-    </tr>
-    {% else %}
-      <tr class="success">
-        <td class="col-md-12" colspan="3">
-          <b> Catégories ajoutées</b>
-        </td>
-      </tr>
-      {% if created_cat_list|length == 0 %}
-        <tr>
-          <td class="col-md-12" colspan="3">Aucune catégorie n'a été ajoutée pour l'instant.</td>
-        </tr>
-      {% else %}
-        {% for cat in created_cat_list %}
-          <tr class="success">
-            <td class="col-md-2">{{ cat.cat_label }}</td>
-            <td class="col-md-8">{{ cat.cat_description}}</td>
-            <td class="col-md-2 text-center">
-              <a title="Détails catégorie"><button class="btn btn-default" id="info_button_created_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button></a>
-              <a href="{{ url_for('cat_editor', cat_id=cat.cat_id, repository=current_repository)}}" title="Editer catégorie" class="btn btn-default"><span class="glyphicon glyphicon glyphicon-pencil"/></a>
-              <a title="Supprimer modifications"><button class="btn btn-default" id="remove_modifs_created_{{ cat.cat_id }}"><span class="glyphicon glyphicon-remove-sign"/></button></a>
-            </td>
-          </tr>
-          <tr class="success">
-            <td colspan="3">
-              <div id="properties_created_{{cat.cat_id}}">
-                <dl class="dl-horizontal">
-                {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
-                {% else %}
-                  {% for (predicate, object) in cat.cat_properties %}
-                    <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
-                    <dd>
-                      {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
-                        {% for cat in total_cat_list %}
-                          {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
-                            {{ cat.cat_label }}
-                          {% endif %}
-                        {% endfor %}
-                      {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
-                        <a href="{{ object }}">{{ object }}</a>
-                      {% else %}
-                        {{ object }}
-                      {% endif %}
-                    </dd>
-                  {% endfor %}
-                {% endif %}
-                </dl>
-              </div>
-              <div id="delete_created_{{cat.cat_id}}">
-                <form method="POST" action="{{ url_for('cat_modifs', deleted_modifs_id=cat.cat_id, repository=current_repository) }}" class="form-inline align-center">
-                  <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                  <fieldset {% if readonly %}disabled{% endif %}>
-                    <div class="input-group">
-                      <div class="input-group-addon">
-                        Vous allez supprimer les changements faits sur cette catégorie.
-                      </div>
-                      <input type="submit" class="btn btn-default" value="Supprimer">
-                    </div>
-                  </fieldset>
-                </form>
-              </div>
-            </td>
-          </tr>
-        {% endfor %}
-      {% endif %}
-      <tr class="warning">
-        <td class="col-md-12" colspan="3">
-          <b> Catégories modifiées</b>
-        </td>
-      </tr>
-      {% if modified_cat_list|length == 0 %}
-      <tr>
-        <td class="col-md-12" colspan="3">Aucune catégorie n'a été modifiée pour l'instant.</td>
-      </tr>
-      {% else %}
-        {% for cat in modified_cat_list %}
-          <tr class="warning">
-            <td class="col-md-2">{{ cat.cat_label }}</td>
-            <td class="col-md-8">{{ cat.cat_description}}</td>
-            <td class="col-md-2 text-center">
-              <a title="Détails catégorie"><button class="btn btn-default" id="info_button_modified_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button></a>
-              <a href="{{ url_for('cat_editor', cat_id=cat.cat_id, repository=current_repository)}}" title="Editer catégorie" class="btn btn-default"><span class="glyphicon glyphicon glyphicon-pencil"/></a>
-              <a title="Supprimer modifications"><button class="btn btn-default" id="remove_modifs_modified_{{ cat.cat_id }}"><span class="glyphicon glyphicon-remove-sign"/></button></a>
-            </td>
-          </tr>
-          <tr class="warning">
-            <td colspan="3">
-              <div id="properties_modified_{{cat.cat_id}}">
-                <dl class="dl-horizontal">
-                {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
-                {% else %}
-                  {% for (predicate, object) in cat.cat_properties %}
-                    <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
-                    <dd>
-                      {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
-                        {% for cat in total_cat_list %}
-                          {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
-                            {{ cat.cat_label }}
-                          {% endif %}
-                        {% endfor %}
-                      {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
-                        <a href="{{ object }}">{{ object }}</a>
-                      {% else %}
-                        {{ object }}
-                      {% endif %}
-                    </dd>
-                  {% endfor %}
-                {% endif %}
-                </dl>
-              </div>
-              <div id="delete_modified_{{cat.cat_id}}">
-                <form method="POST" action="{{ url_for('cat_modifs', deleted_modifs_id=cat.cat_id, repository=current_repository) }}" class="form-inline align-center">
-                  <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                  <fieldset {% if readonly %}disabled{% endif %}>
-                    <div class="input-group">
-                      <div class="input-group-addon">
-                        Vous allez supprimer les changements faits sur cette catégorie.
-                      </div>
-                      <input type="submit" class="btn btn-default" value="Supprimer">
-                    </div>
-                  </fieldset>
-                </form>
-              </div>
-            </td>
-          </tr>
-        {% endfor %}
-      {% endif %}
-
-      <tr class="danger">
-        <td class="col-md-12" colspan="3">
-          <b> Catégories supprimées</b>
-        </td>
-      </tr>
-      {% if deleted_cat_namelist|length == 0 %}
-      <tr>
-        <td class="col-md-12" colspan="3">Aucune catégorie n'a été supprimée pour l'instant.</td>
-      </tr>
-      {% else %}
-        {% for deleted_cat in deleted_cat_namelist %}
-          {% for existing_cat in existing_cat_list %}
-            {% if existing_cat.cat_id == deleted_cat %}
-              <tr class="danger">
-                <td class="col-md-2">{{ existing_cat.cat_label }}</td>
-                <td class="col-md-8"><i>Cette catégorie va être supprimée quand vous soumettrez vos modifications.</i></td>
-                <td class="col-md-2 text-center">
-                  <form method="POST" action="{{ url_for('cat_modifs', deleted_cat_id=deleted_cat, repository=current_repository) }}">
-                    <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                    <fieldset {% if readonly %}disabled{% endif %}>
-                      <input type="submit" class="btn btn-default" value="Restaurer">
-                    </fieldset>
-                  </form>
-                </td>
-              </tr>
-            {% endif %}
-          {% endfor %}
-        {% endfor %}
-      {% endif %}
-    {% endif %}
-    </tbody>
-  </table>
-  <h3> Soumettre mes changements </h3>
-  <div class="col-md-12">
-    <form method="POST" action="{{ url_for('cat_modifs', repository=current_repository)}}">
-      <fieldset {% if readonly %}disabled{% endif %}>
-        {{ commit_form.hidden_tag() }}
-        <div class="form-group">
-          {{ commit_form.commit_message.label }}
-          {{ commit_form.commit_message(size=40, class="form-control", readonly=readonly) }}
-        </div>
-        <button type="submit" class="btn btn-default">Soumettre modifications</button>
-        <a href="{{ url_for('cat_recap', repository=current_repository)}}"class="btn btn-default">Retour</a>
-      </fieldset>
-    </form><br>
-  </div>
-{% endblock page_content %}
--- a/src/catedit/templates/catrecap.html	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-{% extends "catbase.html" %}
-{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
-  {% set readonly="readonly" %}
-{% else %}
-  {% set readonly=False %}
-{% endif %}
-{% block title %}{{current_repository}}: Atelier{% endblock title %}
-{% block head %}
-  {{ super() }}
-  <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
-  <script>
-    $(document).ready(function(){
-      {% for cat in cat_list %}
-      $("#properties_{{cat.cat_id}}").hide();
-      $("#info_button_{{cat.cat_id}}").click(function(){
-        $("#properties_{{cat.cat_id}}").slideToggle();
-      });
-      {% endfor %}
-    });
-  </script>
-{% endblock head %}
-{% block navbar_items %}
-  {{ super() }}
-  {% if session.get("user_logged", None) %}
-    <li><a class="navbar-decorative">></a></li>
-    <li class="active"><a>Atelier</a></li>
-  {% endif %}
-{% endblock navbar_items %}
-{% block repo_list %}
-  {{ super() }}
-{% endblock repo_list %}
-{% block page_content %}
-  <h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
-  <h3> Créer une catégorie : <a href="{{url_for('cat_editor', repository=current_repository)}}" title="Créer catégorie" class="btn btn-default {% if readonly %}disabled{% endif %}"><span class="glyphicon glyphicon-plus"/></a></h3>
-  {% if session["user_logged"] and not session["user_can_edit"] %}
-  <div class="alert alert-warning" role="alert">
-    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-    <span class="sr-only">Attention:</span>
-    Vous n'avez pas accès en écriture au repository contenant les catégories - Vous ne pourrez pas les modifier.
-  </div>
-  {% endif %}
-  <h3>Mes catégories</h3>
-  <table class="table table-bordered table-condensed">
-    <thead>
-      <tr class="active">
-        <th class="col-md-2"><b>Nom de la catégorie</b></th>
-        <th class="col-md-10"><b>Description de la catégorie</b></th>
-      </tr>
-    </thead>
-    <tbody>
-    {% if not session["user_logged"] %}
-    <tr>
-      <td class="col-md-12" colspan="2">
-        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
-        <span class="sr-only">Attention:</span>
-        Veuillez vous identifier pour visualiser les catégories
-      </td>
-    </tr>
-    {% else %}
-      {% if cat_list|length == 0 %}
-        <tr>
-          <td class="col-md-12" colspan="2">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('cat_editor', repository=current_repository) }}">Créer une catégorie</a>{% endif %}</td>
-        </tr>
-      {% else %}
-        {% for cat in cat_list %}
-          <tr
-            {% if cat.state == "created" %}
-              class="success"
-            {% elif cat.state == "modified" %}
-              class="warning"
-            {% elif cat.state == "deleted" %}
-              class="danger"
-            {% endif %}>
-            <td class="col-md-2">{{ cat.cat_label }}</td>
-            <td class="col-md-8">{{ cat.cat_description}}</td>
-            <td class="col-md-1 text-center">
-              <a title="Détails catégorie">
-                <button class="btn btn-default" id="info_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button>
-              </a>
-            </td>
-            {% if (cat.state != "deleted") %}
-            <td class="col-md-1 text-center">
-              <a href="{{ url_for('cat_editor', cat_id=cat.cat_id, repository=current_repository)}}" title="Editer catégorie" class="btn btn-default {% if readonly %}disabled{% endif %}">
-                <span class="glyphicon glyphicon glyphicon-pencil"/>
-              </a>
-            </td>
-            {% endif %}
-            {% if (cat.state == "untouched") %}
-            <td class="col-md-1 text-center">
-              <form method="POST" action="{{url_for('cat_recap', deleted_cat_id=cat.cat_id, repository=current_repository)}}" class="form-inline">
-                <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                <a title="Supprimer catégorie">
-                  <button class="btn btn-default {% if readonly %}disabled{% endif %}" type="submit"><span class="glyphicon glyphicon-trash"/></button>
-                </a>
-              </form>
-            </td>
-            {% elif (cat.state == "created" or cat.state == "modified") %}
-            <td class="col-md-1 text-center">
-              <form method="POST" action="{{url_for('cat_recap', deleted_modifs_id=cat.cat_id, repository=current_repository)}}" class="form-inline">
-                <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                <a title="Supprimer changements">
-                  <button class="btn btn-default"><span class="glyphicon glyphicon-remove-sign"/></button>
-                </a>
-              </form>
-            </td>
-            {% else %}
-            <td colspan="2">
-              <form method="POST" action="{{url_for('cat_recap', deleted_cat_id=cat.cat_id, repository=current_repository)}}" class="form-inline">
-                <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-                <a title="Restaurer catégorie">
-                  <button class="btn btn-default" type="submit">Restaurer</button>
-                </a>
-              </form>
-            </td>
-            {% endif %}
-          </tr>
-          <tr
-            {% if cat.state == "created" %}
-              class="success"
-            {% elif cat.state == "modified" %}
-              class="warning"
-            {% elif cat.state == "deleted" %}
-              class="danger"
-            {% endif %}>
-            <td colspan="5">
-              <div id="properties_{{cat.cat_id}}">
-                <dl class="dl-horizontal">
-                {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
-                {% else %}
-                  {% for (predicate, object) in cat.cat_properties %}
-                    <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
-                    <dd>
-                      {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
-                        {% for cat in cat_list %}
-                          {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
-                            {{ cat.cat_label }}
-                          {% endif %}
-                        {% endfor %}
-                      {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
-                        <a href="{{ object }}">{{ object }}</a>
-                      {% else %}
-                        {{ object }}
-                      {% endif %}
-                    </dd>
-                  {% endfor %}
-                {% endif %}
-                </dl>
-              </div>
-            </td>
-          </tr>
-        {% endfor %}
-      {% endif %}
-    {% endif %}
-    </tbody>
-  </table>
-  {% if session.get("user_logged") %}
-  <form method="POST" action="{{url_for('cat_recap', repository=current_repository)}}" class="form-inline">
-    <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
-    <h4> Annuler tous mes changements actuels :
-      <a title="Supprimer changements">
-        <button type="submit" class="btn btn-default" {% if readonly %}disabled{% endif %}>
-          <span class="glyphicon glyphicon-remove"/>
-        </button>
-      </a>
-    </h4>
-  </form>
-  <h4> Soumettre mes changements actuels : <a href="{{ url_for('cat_modifs', repository=current_repository)}}" title="Soumettre changements" class="btn btn-default" {% if readonly %}disabled{% endif %}><span class="glyphicon glyphicon-share"/></a>
-  </h4>
-  {% endif %}
-  <h3> Consulter l'historique des modifications : <a href="https://github.com/{{config['PERSISTENCE_CONFIG']['REPOSITORY_OWNER']}}/{{current_repository}}/commits/master" title="Aller à l'historique des modifications sur Github" class="btn btn-default"><span class="glyphicon glyphicon-list"/></a></h3>
-  <h3> Consulter les discussions générales sur cet ensemble de catégorie (Issues) : <a href="https://github.com/{{config['PERSISTENCE_CONFIG']['REPOSITORY_OWNER']}}/{{current_repository}}/issues" title="Aller à la page de discussion sur l'ensemble de catégories" class="btn btn-default"><span class="glyphicon glyphicon-comment"/></a></h3>
-{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/home/index.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,86 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %}Page d'accueil{% endblock title %}
+{% block head %}
+  {{ super() }}
+{% endblock head %}
+{% block navbar_items %}
+  <li class="active"><a>Page d'accueil</a></li>
+{% endblock navbar_items %}
+{% block repo_list %}
+  <option value="" selected="selected"> Sélectionnez ensemble... </option>
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+  <h2> <b>CatEdit</b>: Editeur de Catégories</h2>
+  <h3> Mode d'emploi </h3>
+  <div class="col-md-8">
+    <p>
+      CatEdit est un outil permettant de créer et d'éditer des catégories.
+      Vous aurez besoin d'un compte Github pour utiliser CatEdit:
+      <a href="https://github.com/">s'inscrire sur Github</a>
+    </p>
+    <p>
+      Une fois authentifié, choisissez un ensemble de catégorie dans la liste se trouvant
+      dans la barre de navigation pour être redirigé vers l'atelier correspondant.
+      Vous pourrez y trouver la liste des catégories existantes pour cet ensemble,
+      présentée dans un tableau qui initialement ne comprend que les catégories de l'état courant.
+    </p>
+    <p>
+      L'état courant de l'ensemble de catégorie est la base à partir de laquelle
+      vous pourrez créer et éditer des catégories. Une catégorie consiste en un label, unique
+      et non-vide, une description, unique et non-vide et un nombre variable de propriétés.
+    </p>
+    <h4><b>La liste de catégories</b></h4>
+    <p>
+      A chaque fois que vous faites des modifications, elles sont stockées
+      de manière temporaire tant que vous restez authentifié. La liste de catégories
+      dans l'Atelier est mise à jour au fil de vos modifications selon un code couleur
+      vous permettant de visualiser vos changements.
+    </p>
+      <ul>
+        <li>
+          Une ligne de tableau <b>blanche</b> représente une catégorie de l'état courant
+          que vous n'avez ni modifié ni supprimé.
+        </li>
+        <li>
+          Une ligne de tableau <b class="text-success">verte</b> représente une catégorie que vous avez créée de zéro
+          et qui n'existait pas précédemment dans l'état courant.
+        </li>
+        <li>
+          Une ligne de tableau <b class="text-warning">orange</b> représente une catégorie qui existait
+          initialement et que vous avez modifié.
+        </li>
+        <li>
+          Une ligne de tableau <b class="text-danger">rouge</b> représente une catégorie de l'état courant que
+          vous avez supprimé.
+        </li>
+      </ul>
+    </p>
+    <p>
+      Pour que vos modifications soient permanentes et qu'elles deviennent le nouvel état courant
+      de l'ensemble de catégories, vous devez soumettre vos modifications en suivant le lien dans l'Atelier.
+      Vous trouverez une page vous présentant l'état initial d'une part et vos changements d'autre part.
+    </p>
+    <p>
+      Vous devez obligatoirement renseigner une explication de vos changements avant soumission.
+    </p>
+    <h3> Interface de discussion </h3>
+    <p>
+      Chaque soumission de modifications ouvrira sur GitHub un fil de discussion ayant pour titre le message
+      de soumission. Vous pouvez accéder à la liste de changements via un lien dans l'Atelier. Cela vous
+      redirigera vers la page GitHub présentant tous les changements, et vous pouvez accéder à la discussion
+      d'un changement donné en cliquant sur son titre.
+    </p>
+    <p>
+      Il est aussi possible de créer des discussions générales sur un ensemble de catégories. Un lien est disponible
+      dans l'atelier vers la page "Issues" de l'ensemble de catégories sur GitHub, où vous pouvez commenter et créer
+      de nouvelles "Issues" pour alimenter la discussion sur l'ensemble de catégories considéré.
+    </p>
+    <br><br>
+  </div>
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/home/login.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,67 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %} Catedit: Login {% endblock title %}
+{% block head %}
+  {{ super() }}
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  <li><a class="navbar-decorative">></a></li>
+  <li class="active"><a>Authentification</a></li>
+{% endblock navbar_items%}
+{% block repo_list %}
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+<h2> <b>CatEdit</b> - <small>Authentification</small></h2>
+{% if form.user_login.errors or form.user_password.errors %}
+<div class="alert alert-danger">
+  <strong>
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    Erreur:
+  </strong>
+  Vous n'avez pas rempli certains champs obligatoires.
+</div>
+{% endif %}
+<div class="col-md-8">
+  <p>
+    <br>
+    Veuillez entrez votre nom d'utilisateur Github et mot de passe Github.
+  </p>
+  <form method="POST" action="{{url_for('home.login')}}" id="login_form" role="form">
+    <div class="form-group">
+      {% if form.user_login.errors %}
+        {% set login_placeholder="Champ obligatoire" %}
+      {% endif %}
+      {{ form.hidden_tag() }}
+      {{form.user_login.label}}
+      {{form.user_login(class="form-control", id="user_login", placeholder=login_placeholder)}}
+      {{form.user_password.label}}
+      {{form.user_password(class="form-control", id="user_password")}}
+    </div>
+    <button type="submit" class="btn btn-default">Me connecter à CatEdit</button>
+  </form>
+{% if form.user_login.data and not(form.user_login.errors or form.user_password.errors) %}
+  <br>
+  <div class="col-md-8 alert alert-info">
+    <p>
+      Il semble que vous utilisez CatEdit pour la première fois. Veuillez cliquer
+      sur le lien suivant pour vous authentifier sur Github afin de pouvoir utiliser CatEdit.
+    </p>
+    <p>
+      Si ça n'est pas la première fois que vous utilisez CatEdit, vérifiez que vous n'avez pas entré
+      un mauvais nom d'utilisateur/mot de passe. Note: Si vous souhaitez changer d'utilisateur,
+      n'oubliez pas auparavant de vous déconnecter de l'utilisateur courant sur <a href="http://github.com">Github</a>.<br><br>
+    </p>
+    <form method="POST" action="{{url_for('home.login_confirm')}}" id="confirm_form" role="form">
+      <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
+      <button type="submit" class="btn btn-default">M'authentifier sur Github</button>
+    </form>
+  </div>
+{% endif %}
+</div>
+{% endblock page_content%}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/layout.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+  {% block head %}
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>{% block title %}{% endblock title %}</title>
+    <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
+    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
+  {% endblock head %}
+</head>
+<body>
+  <div class="navbar navbar-inverse" role="navigation">
+    <div class="container">
+      <div class="navbar-header">
+        <a class="navbar-brand" href="{{ url_for('home.index') }}">
+          <img alt="Brand" src="{{ url_for('static', filename='img/catedit_brand.png') }}" class="navbar-img" width="32" height="32">
+        </a>
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+          <span class="sr-only">Toggle navigation</span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+        </button>
+      </div>
+      <div class="collapse navbar-collapse">
+        <ul class="nav navbar-nav">
+          {% block navbar_items %}
+            <li><a href="{{ url_for('home.index') }}">Page d'accueil</a></li>
+          {% endblock navbar_items %}
+        </ul>
+        <div class="navbar-text navbar-right">
+          {% if not session.get("user_logged", None)%} Non authentifié - <a href="{{ url_for('home.login') }}" class="navbar-link">S'authentifier</a>
+          {% else %} Authentifié: {{ session["user_login"] }} - <a href="{{ url_for('home.logout') }}" class="navbar-link">Quitter</a>{% endif %}
+        </div>
+        {% if session["user_logged"] %}
+          <form class="navbar-form navbar-right">
+            <select class="form-control select-repo" name="navrepo" onchange="window.location.href=this.form.navrepo.options[this.form.navrepo.selectedIndex].value">
+              {% block repo_list %}
+                {% for repo in session.get("user_repositories", []) %}
+                  <option value="{{url_for('categories.workshop', repository=repo)}}" {% if repo==current_repository %}selected="selected"{% endif %}>{{repo}}</option>
+                {% endfor %}
+              {% endblock repo_list %}
+            </select>
+          </form>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+  <div class="container">
+    {% block page_content %}
+    {% endblock page_content %}
+  </div>
+  <footer class="footer">
+    <div class="text-right footer-notes">
+      CatEdit - Prototype v{{config["CURRENT_VERSION"]}}
+      <br><br>
+    </div>
+  </footer>
+</body>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/macros.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,104 @@
+{% macro category_table(cat_list, current_repository, state_list=["mixed"], target="", interactive=True) -%}
+  {% for cat in cat_list %}
+    {% if state_list == ["mixed"] or (cat.state in state_list) %}
+      <tr {% if cat.state == "created" %}
+        class="success"
+      {% elif cat.state == "modified" %}
+        class="warning"
+      {% elif cat.state == "deleted" %}
+        class="danger"
+      {% endif %}>
+        <td class="col-md-2">{{ cat.cat_label }}</td>
+        <td class="col-md-7">{{ cat.cat_description}}</td>
+        <td class="col-md-1 text-center">
+          <a title="Détails catégorie">
+            <button class="btn btn-default cat-info-toggle" id="info_button_{% if ((cat.state != 'untouched') and (cat.state != 'original')) %}edited_{% endif %}{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button>
+          </a>
+        </td>
+        {% if interactive %}
+          {% if (cat.state != "deleted") %}
+          <td class="col-md-1 text-center">
+            <a href="{{ url_for('categories.editor', cat_id=cat.cat_id, repository=current_repository)}}" title="Editer catégorie" class="btn btn-default {% if readonly %}disabled{% endif %}">
+              <span class="glyphicon glyphicon glyphicon-pencil"/>
+            </a>
+          </td>
+          {% endif %}
+          {% if (cat.state == "untouched") or (cat.state == "original") %}
+          <td class="col-md-1 text-center">
+            <a title="Supprimer catégorie">
+              <button class="btn btn-default cat-delete-toggle {% if readonly %}disabled{% endif %}" id="delete_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon-trash"/></button>
+            </a>
+          </td>
+          {% elif (cat.state == "created" or cat.state == "modified") %}
+          <td class="col-md-1 text-center">
+            <a title="Supprimer changements">
+              <button class="btn btn-default cat-delete-toggle {% if readonly %}disabled{% endif %}" id="delete_button_edited_{{ cat.cat_id }}"><span class="glyphicon glyphicon-remove-sign"/></button>
+            </a>
+          </td>
+          {% else %}
+          <td colspan="2" class="text-center">
+            <form method="POST" action="{{url_for(target, deleted_cat_id=cat.cat_id, repository=current_repository)}}" class="form-inline">
+              <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
+              <a title="Restaurer catégorie">
+                <button class="btn btn-default" type="submit">Restaurer</button>
+              </a>
+            </form>
+          </td>
+          {% endif %}
+        {% endif %}
+      </tr>
+      <tr
+        {% if cat.state == "created" %}
+          class="success"
+        {% elif cat.state == "modified" %}
+          class="warning"
+        {% elif cat.state == "deleted" %}
+          class="danger"
+        {% endif %}>
+        <td colspan="5">
+          <div class="cat-info-div" id="properties_{% if (cat.state != 'untouched') and (cat.state != 'original') %}edited_{% endif %}{{cat.cat_id}}">
+            <dl class="dl-horizontal">
+            {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
+            {% else %}
+              {% for (predicate, object) in cat.cat_properties %}
+                <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
+                <dd>
+                  {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
+                    {% for cat in cat_list %}
+                      {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
+                        {{ cat.cat_label }}
+                      {% endif %}
+                    {% endfor %}
+                  {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
+                    <a href="{{ object }}">{{ object }}</a>
+                  {% else %}
+                    {{ object }}
+                  {% endif %}
+                </dd>
+              {% endfor %}
+            {% endif %}
+            </dl>
+          </div>
+          {% if interactive %}
+            {% if cat.state != "deleted" %}
+            <div class="cat-delete-div" id="delete_confirm_{% if ((cat.state != 'untouched') and (cat.state != 'original')) %}edited_{% endif %}{{cat.cat_id}}">
+              <form method="POST" action=
+                "{% if cat.state == 'modified' %}
+                  {{url_for(target, deleted_changes_id=cat.cat_id, repository=current_repository)}}
+                 {% else %}
+                  {{url_for(target, deleted_cat_id=cat.cat_id, repository=current_repository)}}
+                 {% endif %}" class="form-inline">
+                <label> Vous allez supprimer {% if (cat.state == "modified") or (cat.state == "created") %} les changements sur {% endif %} la catégorie. </label>
+                <input name="csrf_token" value="{{ csrf_token() }}" type="hidden">
+                <a title="Supprimer {% if cat.state == 'modified' %}changements{% else %}catégorie{% endif %}">
+                  <button class="btn btn-default {% if readonly %}disabled{% endif %}" type="submit">Confirmer suppression</button>
+                </a>
+              </form>
+            </div>
+            {% endif %}
+          {% endif %}
+        </td>
+      </tr>
+    {% endif %}
+  {% endfor %}
+{%- endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/social/changeset.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,74 @@
+{% extends "social/comment_thread_layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %}{{super()}}{% endblock title %}
+{% block head %}
+  {{super()}}
+  <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
+  <script>
+    $(document).ready(function(){
+      $(".cat-info-div").hide();
+      $(".cat-table-toggle").click(function(evt){
+        $(".cat-table").slideToggle(function(){
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-up");
+          $(".cat-table-toggle").children().toggleClass("glyphicon-chevron-down");
+        });
+      });
+      $(".cat-info-toggle").click(function(evt){
+        $("#properties_"+evt.target.id.split('_').slice(2,5).join('_')).slideToggle(function(){
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-plus-sign");
+          $("#info_button_"+evt.target.id.split('_').slice(2,5).join('_')).children().toggleClass("glyphicon-minus-sign");
+        });
+      });
+    });
+  </script>
+{% endblock head%}
+{% block navbar_items %}
+  {{super()}}
+  <li><a class="navbar-decorative">></a></li>
+  <li class="active"><a>Discussion: changements</a></li>
+{% endblock navbar_items %}
+{% block additional_content %}
+<h3><strong>Etat de l'ensemble des catégories pour ces modifications </strong>
+  <a title="Afficher/Cacher tableau" class="btn btn-default cat-table-toggle">
+    <span class="glyphicon glyphicon-chevron-up"/>
+  </a>
+</h3>
+<div class="cat-table">
+  <table class="table table-condensed table-bordered">
+    <thead>
+      <tr class="active">
+        <th class="col-md-2"><b>Nom de la catégorie</b></th>
+        <th class="col-md-10" colspan="2"><b>Description de la catégorie</b></th>
+      </tr>
+    </thead>
+    <tbody>
+    {% if not session["user_logged"] %}
+    <tr>
+      <td class="col-md-12" colspan="2">
+        <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+        <span class="sr-only">Attention:</span>
+        Veuillez vous identifier pour visualiser les catégories
+      </td>
+    </tr>
+    {% else %}
+      {% if cat_list|length == 0 %}
+        <tr>
+          <td class="col-md-12" colspan="2">Aucune catégorie n'existait suite à ces changements. {% if not readonly %}<a href="{{ url_for('categories.editor', repository=current_repository) }}">Créer une catégorie</a>{% endif %}</td>
+        </tr>
+      {% else %}
+        {% import "macros.html" as macros %}
+        {{ macros.category_table(cat_list, current_repository, state_list=["original"], interactive=False) }}
+      {% endif %}
+    {% endif %}
+    </tbody>
+  </table>
+</div>
+{% endblock additional_content %}
+{% block comment_posting_target %}{{url_for("social.changeset", changeset_id=changeset_id, repository=current_repository)}}{% endblock %}
+{% block page_content %}
+  {{super()}}
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/social/comment_thread_layout.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,76 @@
+{% extends "layout.html" %}
+{% block title %}{{ current_repository }}: Discussion{% endblock title %}
+{% block head %}
+  {{ super ()}}
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  {% if session.get("user_logged", None) %}
+    <li><a class="navbar-decorative">></a></li>
+    <li><a href="{{ url_for('categories.workshop', repository=current_repository) }}">Atelier</a></li>
+    <li><a class="navbar-decorative">></a></li>
+    <li><a href="{{ url_for('social.index', repository=current_repository) }}">Social</a></li>
+  {% endif %}
+{% endblock navbar_items %}
+{% block page_content %}
+<h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+{% block additional_content %}
+{% endblock additional_content %}
+<h3><strong>Discussion</strong></h3>
+  <small></small>
+</h3>
+{% if comment_form.comment_field.errors %}
+<div class="alert alert-danger">
+  <strong>
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    Erreur:
+  </strong>
+  Votre commentaire est vide.
+</div>
+{% endif %}
+<table class="table table-striped">
+  <theader>
+    <tr class="info">
+      <th class="col-md-2"> {{comments["author"]}} </td>
+      <th class="col-md-2"> {{comments["opening_date"]}} </td>
+      <th class="col-md-8"> <strong>Titre: {{comments["title"]}}</strong> </td>
+    </tr>
+      <tr>
+        <td colspan="2"/>
+        <td>{{comments["opening_post"]}}</td>
+      </tr>
+  </theader>
+  <tbody>
+    {% if comments["comment_list"]|length == 0 %}
+      <tr>
+        <td colspan="3"> Aucun commentaire n'a été posté pour le moment </td>
+      </tr>
+    {% else %}
+      {% for comment in comments["comment_list"] %}
+        <tr>
+          <td class="col-md-2"> {{comment["author"]}} </td>
+          <td class="col-md-2"> {{comment["date"]}} </td>
+          <td class="col-md-8"> {{comment["body"]}} </td>
+        </tr>
+      {% endfor %}
+    {% endif %}
+    <tr>
+      <td colspan="2" class="text-right">
+        {{ comment_form.comment_field.label }}
+      </td>
+      <td>
+        <form method="POST" action="{% block comment_posting_target %}{% endblock %}">
+          <fieldset {% if readonly %}disabled{% endif %}>
+            {{ comment_form.hidden_tag() }}
+            <div class="form-group">
+              {{ comment_form.comment_field(class="form-control", readonly=readonly) }}
+            </div>
+            <button type="submit" class="btn btn-default">Envoyer commentaire</button>
+            <a href="{{ url_for('social.index', repository=current_repository)}}"class="btn btn-default">Retour</a>
+          </fieldset>
+        </form><br>
+      </td>
+    </tr>
+  </tbody>
+</table>
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/social/discussion.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,48 @@
+{% extends "social/comment_thread_layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %}{{super()}}{% endblock title %}
+{% block head %}
+  {{super()}}
+{% endblock head%}
+{% block navbar_items %}
+  {{super()}}
+  <li><a class="navbar-decorative">></a></li>
+  <li class="active"><a>Discussion</a></li>
+{% endblock navbar_items %}
+{% block comment_posting_target %}{{url_for("social.discussion", discussion_id=discussion_id, repository=current_repository)}}{% endblock %}
+{% block page_content %}
+  {% if discussion_id == "new" %}
+  <h2><b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+  <h3>Nouvelle discussion</h3>
+  {% if comment_form.comment_field.errors or comment_form.discussion_title.errors %}
+  <div class="alert alert-danger">
+    <strong>
+      <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+      Erreur:
+    </strong>
+    Vous devez renseigner un titre pour votre discussion.
+  </div>
+  {% endif %}
+  <form method="POST" action="{{url_for('social.discussion', discussion_id='new', repository=current_repository)}}">
+    <fieldset {% if readonly %}disabled{% endif %}>
+      {{ comment_form.hidden_tag() }}
+      <div class="form-group">
+        <br>
+        {{ comment_form.discussion_title.label }}
+        {{ comment_form.discussion_title(class="form-control", readonly=readonly) }}
+        <br>
+        {{ comment_form.comment_field.label }}
+        {{ comment_form.comment_field(class="form-control", readonly=readonly) }}
+      </div>
+      <button type="submit" class="btn btn-default">Enregistrer</button>
+      <a href="{{ url_for('social.index', repository=current_repository)}}"class="btn btn-default">Retour</a>
+    </fieldset>
+  </form>
+  {% else %}
+    {{super()}}
+  {% endif %}
+{% endblock page_content %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/social/index.html	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,99 @@
+{% extends "layout.html" %}
+{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %}
+  {% set readonly="readonly" %}
+{% else %}
+  {% set readonly=False %}
+{% endif %}
+{% block title %} {{ current_repository}}: Espaces de discussion {% endblock title %}
+{% block head %}
+  {{ super() }}
+{% endblock head %}
+{% block navbar_items %}
+  {{ super() }}
+  {% if session.get("user_logged", None) %}
+    <li><a class="navbar-decorative">></a></li>
+    <li><a href="{{ url_for('categories.workshop', repository=current_repository) }}">Atelier</a></li>
+    <li><a class="navbar-decorative">></a></li>
+    <li class="active"><a>Social</a></li>
+  {% endif %}
+{% endblock navbar_items %}
+{% block repo_list %}
+  {{ super() }}
+{% endblock repo_list %}
+{% block page_content %}
+  <h2> <b>CatEdit</b> - <small>{{current_repository}}</small></h2>
+  {% if readonly %}
+  <div class="alert alert-warning" role="alert">
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    <span class="sr-only">Attention:</span>
+    Vous n'avez pas accès en écriture au repository contenant les catégories - Vous ne pourrez pas poster de commentaires
+  </div>
+  {% endif %}
+  <h3>Historique des modifications</h3>
+  <table class="table table-condensed">
+    <thead>
+      <tr>
+        <th>Auteur</th>
+        <th>Date</th>
+        <th>Message de soumission</th>
+        <th>Commentaires</th>
+      </tr>
+    </thead>
+    <tbody>
+    {% for changeset in changeset_list %}
+      <tr>
+        <td class="col-md-1">{{changeset["author"]}}</td>
+        <td class="col-md-2">{{changeset["date"]}}</td>
+        <td class="col-md-6">{{changeset["title"]}}</td>
+        <td class="col-md-2">{{changeset["comment_count"]}}</td>
+        <td class="col-md-1">
+          <a href="{{ url_for('social.changeset', repository=current_repository, changeset_id=changeset['id'])}}" title="Voir modifications" class="btn btn-default">
+            <span class=" glyphicon glyphicon-log-in"/>
+          </a>
+        </td>
+      </tr>
+    {% endfor %}
+    </tbody>
+  </table>
+  <h3>Discussions générales</h3>
+  <h4> Créer une nouvelle discussion :
+    <a href="{{ url_for('social.discussion', repository=current_repository, discussion_id='new')}}" title="Créer discussion" class="btn btn-default">
+      <span class=" glyphicon glyphicon-plus"/>
+    </a>
+  </h4>
+  <table class="table table-condensed">
+    <thead>
+      <tr>
+        <th>Auteur</th>
+        <th>Date</th>
+        <th>Titre</th>
+        <th>Commentaires</th>
+        <th>Dernier commentaire</th>
+      </tr>
+    </thead>
+    <tbody>
+  {% if not discussion_list %}
+    <tr>
+      <td colspan="">
+        Il n'y a pas encore de discussion pour cette ensemble de catégories. <a href="#">Créer une discussion</a>
+      </td>
+    </tr>
+  {% else %}
+    {% for discussion in discussion_list %}
+      <tr>
+        <td class="col-md-1">{{discussion["author"]}}</td>
+        <td class="col-md-2">{{discussion["opening_date"]}}</td>
+        <td class="col-md-5">{{discussion["title"]}}</td>
+        <td class="col-md-1">{{discussion["comment_count"]}}</td>
+        <td class="col-md-2">{{discussion["last_updated"]}}</td>
+        <td class="col-md-1">
+          <a href="{{ url_for('social.discussion', repository=current_repository, discussion_id=discussion['id'])}}" title="Voir la discussion" class="btn btn-default">
+            <span class=" glyphicon glyphicon-log-in"/>
+          </a>
+        </td>
+      </tr>
+    {% endfor %}
+  {% endif %}
+    </tbody>
+  </table>
+{% endblock page_content%}
--- a/src/catedit/version.py	Fri Jan 30 12:41:40 2015 +0100
+++ b/src/catedit/version.py	Fri Feb 13 17:22:21 2015 +0100
@@ -6,7 +6,7 @@
 
 __all__ = ["VERSION", "get_version", "CURRENT_VERSION"]
 
-VERSION = (0, 1, 3)
+VERSION = (0, 1, 4)
 
 def get_version():
     """
--- a/src/catedit/views.py	Fri Jan 30 12:41:40 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,717 +0,0 @@
-"""
-views.py:
-The views functions that handle the front-end of the application
-"""
-
-from catedit import app, github, cache
-from catedit.models import Category
-from requests import get
-from requests.auth import HTTPBasicAuth
-import catedit.persistence
-from flask import render_template, request, redirect, url_for, session, abort
-from flask.ext.github import GitHubError
-from flask_wtf import Form
-from catedit.resources import CategoryAPI, CategoryChangesAPI
-from wtforms import StringField, TextAreaField, FormField, PasswordField, \
-                    FieldList, HiddenField
-from wtforms.validators import DataRequired, Optional, AnyOf
-from rdflib import Graph
-from io import StringIO
-
-logger = app.logger
-
-@app.route('/', methods=['GET'])
-@app.route('/index', methods=['GET'])
-def cat_index():
-    """
-        View for index page (for now it's only a readme text so no computing
-        is required)
-    """
-    return render_template("catindex.html")
-
-
-@app.route('/<string:repository>/catrecap',
-           defaults={'deleted_cat_id': None, 'deleted_modifs_id': None},
-           methods=['GET', 'POST'])
-@app.route('/<string:repository>/catrecap/delete-modifs-<deleted_modifs_id>',
-           defaults={'deleted_cat_id': None},
-           methods=['POST'])
-@app.route('/<string:repository>/catrecap/delete-<deleted_cat_id>',
-           defaults={'deleted_modifs_id': None},
-           methods=['POST'])
-def cat_recap(repository, deleted_cat_id, deleted_modifs_id):
-    """
-        View that has a list of all categories available. Template is
-        catrecap.html, located in src/templates/
-
-        Note: it also handles category deletion from the same page.
-    """
-    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
-        abort(404)
-
-    cat_api_instance = CategoryAPI()
-    cat_changes_api_instance = CategoryChangesAPI()
-
-    if request.method == "POST":
-        if (session.get("user_logged", None) is not None and
-                session.get("user_can_edit", False) is not False):
-            if deleted_modifs_id is None and deleted_cat_id is None:
-                cat_changes_api_instance.delete(repository=repository)
-            elif deleted_modifs_id is not None:
-                logger.debug("Changes for category "
-                             + deleted_modifs_id
-                             + " will be deleted.")
-                cat_changes_api_instance.delete(
-                    repository=repository,
-                    modified_cat_id=deleted_modifs_id
-                )
-            # We identify if we want to delete a category
-            elif deleted_cat_id is not None:
-                logger.debug("Category "
-                             + deleted_cat_id
-                             + " will be deleted.")
-                cat_api_instance.delete(
-                    repository=repository,
-                    deleted_cat_id=deleted_cat_id
-                )
-        return redirect(url_for('cat_recap', repository=repository))
-    elif request.method == "GET":
-
-        deleted_cat_dict = {}
-        modified_cat_dict = {}
-        serialized_cat_list = []
-        if session.get("user_logged", None) is not None:
-            serialized_cat_list = cat_api_instance.get(repository=repository) \
-                                                      [0]
-            cat_changes = cat_changes_api_instance.get(repository=repository) \
-                                                      [0]
-            modified_cat_dict = cat_changes["modified_categories"]
-            deleted_cat_dict = cat_changes["deleted_categories"]
-        # logger.debug(serialized_cat_list)
-        cat_list = []
-        original_cat_list = []
-        for serialized_cat in serialized_cat_list:
-            cat_rdf_graph = Graph()
-            cat_rdf_graph.parse(source=StringIO(serialized_cat),
-                                format='turtle')
-            original_cat_list.append(Category(graph=cat_rdf_graph))
-
-        edited_cat_list = []
-        for modified_cat_name in modified_cat_dict.keys():
-            new_cat_rdf_graph = Graph()
-            new_cat_rdf_graph.parse(
-                source=StringIO(
-                    modified_cat_dict[modified_cat_name]
-                ),
-                format='turtle'
-            )
-            edited_cat_list.append(Category(graph=new_cat_rdf_graph))
-        # first we find the untouched, edited and deleted categories
-        cat_state = ""
-        for category in original_cat_list:
-            if category.cat_id not in modified_cat_dict.keys():
-                if category.cat_id in deleted_cat_dict.keys():
-                    cat_state = "deleted"
-                else:
-                    cat_state = "untouched"
-
-                cat_list.append({"cat_label": category.label,
-                                 "cat_description": category.description,
-                                 "cat_id": category.cat_id,
-                                 "cat_properties": category.properties,
-                                 "state": cat_state})
-
-        # now we must find the not yet submitted categories that were created
-        cat_state = ""
-        logger.debug("Edited cat list: "
-                     + str([cat.label for cat in edited_cat_list])
-                     + " - Original cat list: "
-                     + str([cat.label for cat in original_cat_list]))
-        for category in edited_cat_list:
-            if category.cat_id not in [cat.cat_id for
-                                       cat in original_cat_list]:
-                cat_state = "created"
-            else:
-                cat_state = "modified"
-            cat_list.append({"cat_label": category.label,
-                             "cat_description": category.description,
-                             "cat_id": category.cat_id,
-                             "cat_properties": category.properties,
-                             "state": cat_state})
-
-            # logger.debug(c.properties)
-        cat_list = sorted(cat_list, key=lambda cat: cat['cat_id'])
-        return render_template('catrecap.html',
-                               cat_list=cat_list,
-                               current_repository=repository)
-
-class CommitForm(Form):
-    """
-        Custom form class for commiting changes
-    """
-    commit_message = StringField(
-        "Message de soumission (obligatoire)",
-        validators=[DataRequired()]
-    )
-
-
-@app.route('/<string:repository>/catmodifs/delete-modifs-<deleted_modifs_id>',
-           defaults={'deleted_cat_id': None},
-           methods=['POST'])
-@app.route('/<string:repository>/catmodifs/delete-<deleted_cat_id>',
-           defaults={'deleted_modifs_id': None},
-           methods=['POST'])
-@app.route('/<string:repository>/catmodifs',
-           defaults={'deleted_cat_id': None, 'deleted_modifs_id': None},
-           methods=['GET', 'POST'])
-def cat_modifs(repository, deleted_cat_id, deleted_modifs_id):
-    """
-        View that handles all the visualisation of changes for a user's
-        session, links to the editor forms, allows the users to cancel their
-        own changes and submits all their changes.
-    """
-    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
-        abort(404)
-
-    logger.debug(repository)
-
-    changes_list = []
-
-    existing_cat_list = []
-    modified_cat_list = []
-    created_cat_list = []
-    total_cat_list = []
-
-    deleted_cat_namelist = []
-    modified_cat_namelist = []
-
-    cat_api_instance = CategoryAPI()
-    cat_changes_api_instance = CategoryChangesAPI()
-
-    if deleted_cat_id is None and deleted_modifs_id is None:
-
-        commit_form = CommitForm(request.form)
-
-        # if it's a GET with no delete_cat_id or deleted_modifs_id, then we'll
-        # display the changes
-        if request.method == "GET":
-            if session.get("user_logged", None) is not None:
-                serialized_cat_list = cat_api_instance.get(
-                    repository=repository
-                )[0]
-                changes_list = cat_changes_api_instance.get(
-                    repository=repository
-                )[0]
-
-                cat_data = dict({})
-
-                # Creating existing cat list
-                for serialized_cat in serialized_cat_list:
-                    cat_rdf_graph = Graph()
-                    cat_rdf_graph.parse(source=StringIO(serialized_cat),
-                                        format='turtle')
-                    cat = Category(graph=cat_rdf_graph)
-
-                    cat_data = {
-                        "cat_label": cat.label,
-                        "cat_description": cat.description,
-                        "cat_id": cat.cat_id,
-                        "cat_properties": cat.properties
-                    }
-
-                    existing_cat_list.append(cat_data)
-                    total_cat_list.append(cat_data)
-                    cat_data = dict({})
-
-                # Creating modified and created cat lists
-                for modified_cat_name in \
-                        changes_list.get("modified_categories", {}).keys():
-                    modified_cat_rdf_graph = Graph()
-                    modified_cat_rdf_graph.parse(
-                        source=StringIO(
-                            changes_list["modified_categories"]
-                                        [modified_cat_name]
-                        ),
-                        format='turtle'
-                    )
-                    modified_cat = Category(graph=modified_cat_rdf_graph)
-
-                    cat_data = {
-                        "cat_label": modified_cat.label,
-                        "cat_description": modified_cat.description,
-                        "cat_id": modified_cat.cat_id,
-                        "cat_properties": modified_cat.properties
-                    }
-                    if modified_cat.cat_id in \
-                            [existing_cat["cat_id"]
-                                for existing_cat in existing_cat_list]:
-                        modified_cat_list.append(cat_data)
-                    else:
-                        created_cat_list.append(cat_data)
-                        total_cat_list.append(cat_data)
-                    cat_data = dict({})
-
-                # Creating deleted cat list
-                deleted_cat_namelist = list(
-                    changes_list["deleted_categories"].keys()
-                )
-                modified_cat_namelist = list(
-                    changes_list["modified_categories"].keys()
-                )
-                logger.debug("total:"+str(total_cat_list))
-                logger.debug("existing:"+str(existing_cat_list))
-                logger.debug("created:"+str(created_cat_list))
-                logger.debug("modified:"+str(modified_cat_list))
-                logger.debug("modified names:"+str(modified_cat_namelist))
-                logger.debug("deleted names:"+str(deleted_cat_namelist))
-
-            return render_template('catmodifs.html',
-                                   total_cat_list=total_cat_list,
-                                   existing_cat_list=existing_cat_list,
-                                   created_cat_list=created_cat_list,
-                                   modified_cat_list=modified_cat_list,
-                                   modified_cat_namelist=modified_cat_namelist,
-                                   deleted_cat_namelist=deleted_cat_namelist,
-                                   commit_form=commit_form,
-                                   current_repository=repository)
-        # If it's a POST with no delete_cat_id and delete_modifs_id, then we
-        # will submit all the stored changes
-        elif request.method == "POST":
-            if commit_form.validate_on_submit():
-                cat_api_instance.put(repository=repository)
-                cat_changes_api_instance.delete(repository=repository)
-            return redirect(url_for('cat_recap', repository=repository))
-
-    # One of the ids is not None (either deleting a category or a modification)
-    else:
-        # We only do that if we have a POST
-        if request.method == "POST":
-            # We identify if we want to delete a change
-            if (session.get("user_logged", None) is not None) \
-                and \
-               (session.get("user_can_edit", False) is not False):
-                if deleted_modifs_id is not None:
-                    cat_changes_api_instance.delete(
-                        repository=repository,
-                        modified_cat_id=deleted_modifs_id
-                    )
-                # We identify if we want to delete a category
-                elif deleted_cat_id is not None:
-                    cat_api_instance.delete(
-                        repository=repository,
-                        deleted_cat_id=deleted_cat_id
-                    )
-        return redirect(url_for('cat_modifs', repository=repository))
-
-
-
-class PropertyForm(Form):
-    """
-        Form of a given property, each one is a couple of hidden fields that
-        can be Javascript-generated in the template
-    """
-    property_predicate = HiddenField(
-        validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())]
-    )
-    property_object = HiddenField(
-        validators=[DataRequired()]
-    )
-
-
-class CategoryForm(Form):
-    """
-        Custom form class for creating a category with the absolute minimal
-        attributes (label and description)
-    """
-    label = StringField(
-        "Nom de la categorie (obligatoire)",
-        validators=[DataRequired()]
-    )
-    description = TextAreaField(
-        "Description de la categorie (obligatoire)",
-        validators=[DataRequired()]
-    )
-    properties = FieldList(FormField(PropertyForm), validators=[Optional()])
-
-
-@app.route('/<string:repository>/cateditor',
-           methods=['GET', 'POST'])
-@app.route('/<string:repository>/cateditor/<string:cat_id>',
-           methods=['GET', 'POST'])
-def cat_editor(repository, cat_id=None):
-    """
-        View that handles creation and edition of categories. Template is
-        cateditor.html, located in src/templates
-    """
-    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
-        abort(404)
-
-    cat_api_instance = CategoryAPI()
-    cat_changes_api_instance = CategoryChangesAPI()
-
-    specific_serialized_cat = ""
-    # Serialization of the category of id cat_id, either from
-    # CategoryChangesAPI (if it was modified) or from CategoryAPI (if it
-    # was not)
-
-    changes_response = None
-    # Changes on the category cat_id we get from CategoryChangesAPI
-
-    current_cat = None
-    current_cat_id = None
-    current_cat_properties = []
-    # Args for the template, if we create a new category they are not used
-
-    cat_list = []
-    # Category list that will be used by the template
-    deleted_cat_dict = {}
-    # Deleted categories we won't append to cat_list
-    modified_cat_dict = {}
-    # Modified categories to append to cat_list in case label changed
-    serialized_cat_list = []
-    # Existing categories we get from CategoryAPI
-    cat_changes = {}
-    # Changes list we get from CategoryChangesAPI
-
-    if cat_id is not None:
-        if session.get("user_logged", None) is not None:
-            changes_response = cat_changes_api_instance.get(
-                repository=repository,
-                modified_cat_id=cat_id
-            )
-            if changes_response[0]["type"] is "deleted":
-                abort(404)
-            elif changes_response[0]["type"] is "modified":
-                specific_serialized_cat = changes_response[0] \
-                                                          ["category"] \
-                                                          [cat_id]
-            elif changes_response[0]["type"] is "untouched":
-                specific_serialized_cat = cat_api_instance.get(
-                    repository=repository,
-                    cat_id=cat_id
-                )[0]
-        logger.debug(specific_serialized_cat)
-
-        cat_rdf_graph = Graph()
-        cat_rdf_graph.parse(source=StringIO(specific_serialized_cat),
-                            format='turtle')
-
-        current_cat = Category(graph=cat_rdf_graph)
-        current_cat_id = current_cat.cat_id
-        current_cat_properties = current_cat.properties
-        logger.debug(current_cat.properties)
-
-    serialized_cat_list = cat_api_instance.get(repository)[0]
-    cat_changes = cat_changes_api_instance.get(repository)[0]
-    deleted_cat_dict = cat_changes["deleted_categories"]
-    modified_cat_dict = cat_changes["modified_categories"]
-    logger.debug(changes_response)
-
-    for modified_cat_name in modified_cat_dict.keys():
-        modified_cat_rdf_graph = Graph()
-        modified_cat_rdf_graph.parse(
-            source=StringIO(modified_cat_dict[modified_cat_name]),
-            format='turtle'
-        )
-        modified_cat = Category(graph=modified_cat_rdf_graph)
-        cat_list.append({"cat_label": modified_cat.label,
-                         "cat_description": modified_cat.description,
-                         "cat_id": modified_cat.cat_id,
-                         "cat_properties": modified_cat.properties})
-
-    for serialized_cat in serialized_cat_list:
-        cat_rdf_graph = Graph()
-        cat_rdf_graph.parse(source=StringIO(serialized_cat),
-                            format='turtle')
-        cat = Category(graph=cat_rdf_graph)
-        if cat.cat_id not in deleted_cat_dict.keys() and \
-           cat.cat_id not in modified_cat_dict.keys():
-            cat_list.append({"cat_label": cat.label,
-                             "cat_description": cat.description,
-                             "cat_id": cat.cat_id,
-                             "cat_properties": cat.properties})
-
-    cat_form = CategoryForm(request.form)
-
-    if request.method == "GET":
-        """
-            GET Method means we will display the editor
-        """
-        if current_cat is not None:
-            cat_form.label.data = current_cat.label
-            cat_form.description.data = current_cat.description
-            for (cat_predicate, cat_object) in current_cat_properties:
-                if app.config["PROPERTY_LIST"] \
-                             [cat_predicate] \
-                             ["object_type"] == "uriref-category":
-                    namespace, object_id = cat_object.split("#",1)
-                else:
-                    object_id = cat_object
-                cat_form.properties.append_entry({
-                    "property_predicate": cat_predicate,
-                    "property_object": object_id
-                })
-        # list of category that will be used in property editor, list of dict
-        # {"cat_id": cat.cat_id,
-        #  "cat_label": cat.label,
-        #  "cat_description": cat.description,
-        #  "cat_properties": cat.properties}
-        logger.debug("category list that can be linked :" + str(cat_list))
-        logger.debug(
-            "deleted categories list that can't be linked :"
-            + str(list(deleted_cat_dict.keys()))
-        )
-        return render_template('cateditor.html',
-                               cat_id=current_cat_id,
-                               cat_properties=current_cat_properties,
-                               form=cat_form,
-                               cat_list=cat_list,
-                               deleted_cat_list=list(deleted_cat_dict.keys()),
-                               current_repository=repository)
-    elif request.method == "POST":
-        """
-            POST Method means we will compute the form data, call the relevant
-            "POST" and "PUT" method on CategoryAPI, and return to a display
-            page (catrecap for now, ideally the one from whence we came)
-        """
-        if (session.get("user_logged", None) is not None and
-                session.get("user_can_edit", False) is not False):
-
-            if cat_form.validate_on_submit():
-                cat_data = {}
-                cat_data["label"] = cat_form.label.data
-                cat_data["description"] = cat_form.description.data
-                cat_data["properties"] = [
-                    (cat_property["property_predicate"],
-                     cat_property["property_object"])
-                    for cat_property in cat_form.properties.data
-                ]
-
-                if cat_id is not None:
-                    logger.debug(str(cat_data))
-                    cat_api_instance.put(
-                        repository=repository,
-                        cat_id=cat_id,
-                        cat_data=cat_data
-                    )
-                else:
-                    logger.debug(str(cat_data))
-                    cat_api_instance.post(
-                        repository=repository,
-                        cat_data=cat_data
-                    )
-                return redirect(url_for('cat_recap', repository=repository))
-            else:
-                # if form doesn't validate we don't want to delete whatever
-                # changes the user did
-                return render_template('cateditor.html',
-                                       cat_id=cat_id,
-                                       cat_properties=current_cat_properties,
-                                       form=cat_form,
-                                       cat_list=cat_list,
-                                       deleted_cat_list=list(
-                                           deleted_cat_dict.keys()
-                                       ),
-                                       current_repository=repository)
-
-        # If user wasn't logged or couldn't edit but somehow submitted a POST
-        else:
-            return redirect(url_for('cat_index'))
-
-
-class LoginForm(Form):
-    """
-        Custom form class for commiting changes
-    """
-    user_login = StringField(
-        "Nom d'utilisateur Github",
-        validators=[DataRequired()]
-    )
-    user_password = PasswordField(
-        "Mot de passe Github",
-        validators=[DataRequired()]
-    )
-
-
-@app.route('/catedit-login', methods=["GET", "POST"])
-def github_login():
-    """
-        Function that manages authentication (Github), login
-
-        Note: If Persistence is set to PersistenceToFile (categories stored
-        in local files, used for debugging), creates a mock user named
-        "FileEditUser"
-    """
-    if not session.get("user_logged", False):
-        session["modified_categories"] = {
-            repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
-                                           ["REPOSITORY_LIST"]
-        }
-        session["deleted_categories"] = {
-            repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
-                                           ["REPOSITORY_LIST"]
-        }
-        if app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToGithub":
-            login_form = LoginForm(request.form)
-            if request.method == "POST":
-                if login_form.validate_on_submit():
-                    # We'll try to get the auth token for given username
-                    attempted_login = login_form.user_login.data
-                    try:
-                        auth_response = get(
-                            "https://api.github.com/"
-                            + "authorizations",
-                            auth=HTTPBasicAuth(
-                                login_form.user_login.data,
-                                login_form.user_password.data
-                            )
-                        )
-                        for auth in auth_response.json():
-                            if auth["app"]["client_id"] \
-                            == app.config["GITHUB_CLIENT_ID"]:
-                                session["user_code"] = auth["token"]
-                                session["user_logged"] = True
-                    except:
-                        logger.debug(
-                            "Error requesting authorizations for"
-                            + " user. Either the user is new to catedit, or "
-                            + "entered a wrong username/password"
-                        )
-                    logger.debug(
-                        "user token found by request: "
-                        + str(session.get("user_code", None))
-                    )
-                    if session.get("user_code", None) == None:
-                        # We didn't get it, so we direct the user to the login page
-                        # with a link to github oauth system
-                        return render_template(
-                            'catlogin.html',
-                            form = login_form
-                        )
-                    else:
-                        # we did get it, so we redirect to callback function
-                        # to wrap up user auth
-                        return redirect(url_for('github_callback'))
-                else:
-                    # form didn't validate, so we send it back to user
-                    return render_template(
-                        'catlogin.html',
-                        form = login_form
-                    )
-            elif request.method == "GET":
-                # We'll render the login form
-                return render_template(
-                    'catlogin.html',
-                    form=login_form,
-                )
-        elif app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToFile":
-            session["user_logged"] = True
-            session["user_can_edit"] = {}
-            session["user_can_edit"]["local"] = True
-            session["user_login"] = "FileEditUser"
-            return redirect(url_for('cat_index'))
-    else:
-        return redirect(url_for('cat_index'))
-
-@app.route('/catedit-login-confirm', methods=["GET", "POST"])
-def github_login_confirm():
-    """
-        Function called if the user is new or revoked the auth token
-    """
-    if not session.get("user_logged", False):
-        if request.method == "POST":
-            return github.authorize(
-                scope="repo",
-                redirect_uri=url_for('github_callback', _external=True)
-            )
-    else:
-        return redirect(url_for('cat_index'))
-
-@app.route('/catedit-github-callback')
-@github.authorized_handler
-def github_callback(oauth_code):
-    """
-        Function that handles callback from Github after succesful login
-    """
-    session.permanent = False
-    if session.get("user_code", None) == None:
-    # That means we got here using github callback and not the login form
-        session["user_code"] = oauth_code
-    logger.debug(session["user_code"])
-    session["user_logged"] = True
-    session["user_login"] = "auth-error"
-    try:
-        logger.debug(
-            "after login: "
-            + str(github.get("rate_limit")["resources"])
-        )
-        session["user_login"] = github.get("user")["login"]
-    except GitHubError as ghe:
-        logger.error(
-            "GitHubError trying to get the user login"
-        )
-        logger.error(ghe.response.text)
-    try:
-        repo_list = []
-        repo_list = github.get("user/repos")
-        logger.debug(str(github.get("rate_limit")["resources"]))
-        for repo in repo_list:
-            logger.debug(repo["name"])
-        user_repos_name = [repo["name"] for repo in repo_list]
-        logger.debug(
-            str(user_repos_name) + " "
-            + str(app.config["PERSISTENCE_CONFIG"]
-                            ["REPOSITORY_LIST"])
-        )
-        session["user_repositories"] = list(
-            set(user_repos_name).intersection(
-                app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]
-            )
-        )
-        session["user_can_edit"] = {}
-        for repo in session["user_repositories"]:
-            if repo in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
-                session["user_can_edit"][repo] = True
-        logger.debug(session["user_can_edit"])
-    except GitHubError as ghe:
-        logger.error(
-            "GitHubError trying to get the list of repository for user "
-            + session["user_login"]
-        )
-        logger.error(ghe.response.text)
-    return redirect(url_for('cat_index'))
-
-
-@github.access_token_getter
-def token_getter():
-    """
-        Utility function for github-flask module to get user token when
-        making authenticated requests
-    """
-    if session.get("user_logged", None):
-        # logger.debug("I made an authentified request")
-        return session["user_code"]
-
-
-@app.route('/catedit-logout')
-def logout():
-    """
-        Function that manages authentication (Github), logout
-
-        Note: if you want to switch github users, you will have to logout of
-        Github, else when logging back in, github will send the app the
-        same oauth code
-    """
-    session["user_logged"] = None
-    session["user_code"] = None
-    session["user_login"] = None
-    session["user_can_edit"] = None
-    session["modified_categories"] = {
-        repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
-                                       ["REPOSITORY_LIST"]
-    }
-    session["deleted_categories"] = {
-        repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
-                                       ["REPOSITORY_LIST"]
-    }
-    return redirect(url_for('cat_index'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/views/__init__.py	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,1 @@
+__all__ = ["home", "categories", "social"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/views/categories.py	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,494 @@
+"""
+categories.py:
+The views functions that handle category management and editing
+"""
+
+from catedit import app
+from catedit.models import Category
+from views.utils import check_user_status
+from flask import render_template, request, redirect, url_for, session, \
+                  abort, Blueprint
+from flask_wtf import Form
+from catedit.resources import CategoryAPI, CategoryChangesAPI
+from wtforms import StringField, TextAreaField, FormField, \
+                    FieldList, HiddenField
+from wtforms.validators import DataRequired, Optional, AnyOf
+from rdflib import Graph
+from io import StringIO
+
+module = Blueprint('categories', __name__)
+logger = app.logger
+
+def make_category_list(repository, without_changes=False):
+    """
+        Shortcut function that generates the list of category to use for
+        view templates.
+        Each category is a dict with the following format:
+        {
+            "cat_label": category label,
+             "cat_description": category description,
+             "cat_id": category id,
+             "cat_properties": category properties,
+             "state": category state (one of {"untouched", "created",
+             "edited", "deleted"})
+        }
+    """
+    cat_api_instance = CategoryAPI()
+    cat_changes_api_instance = CategoryChangesAPI()
+
+    deleted_cat_dict = {}
+    modified_cat_dict = {}
+    serialized_cat_list = []
+    if session.get("user_logged", None) is not None:
+        serialized_cat_list = cat_api_instance.get(repository=repository) \
+                                                  [0]
+        cat_changes = cat_changes_api_instance.get(repository=repository) \
+                                                  [0]
+        modified_cat_dict = cat_changes["modified_categories"]
+        deleted_cat_dict = cat_changes["deleted_categories"]
+    # logger.debug(serialized_cat_list)
+    cat_list = []
+    original_cat_list = []
+    for serialized_cat in serialized_cat_list:
+        cat_rdf_graph = Graph()
+        cat_rdf_graph.parse(source=StringIO(serialized_cat),
+                            format='turtle')
+        original_cat_list.append(Category(graph=cat_rdf_graph))
+
+    if without_changes:
+        for category in original_cat_list:
+            cat_list.append(
+                {
+                    "cat_label": category.label,
+                    "cat_description": category.description,
+                    "cat_id": category.cat_id,
+                    "cat_properties": category.properties,
+                    "state": "original"
+                }
+            )
+    else:
+        edited_cat_list = []
+        for modified_cat_name in modified_cat_dict.keys():
+            new_cat_rdf_graph = Graph()
+            new_cat_rdf_graph.parse(
+                source=StringIO(
+                    modified_cat_dict[modified_cat_name]
+                ),
+                format='turtle'
+            )
+            edited_cat_list.append(Category(graph=new_cat_rdf_graph))
+        # first we find the untouched, edited and deleted categories
+        cat_state = ""
+        for category in original_cat_list:
+            if category.cat_id not in modified_cat_dict.keys():
+                if category.cat_id in deleted_cat_dict.keys():
+                    cat_state = "deleted"
+                else:
+                    cat_state = "untouched"
+
+                cat_list.append(
+                    {
+                        "cat_label": category.label,
+                        "cat_description": category.description,
+                        "cat_id": category.cat_id,
+                        "cat_properties": category.properties,
+                        "state": cat_state
+                    }
+                )
+
+        # now we must find the not yet submitted categories that were created
+        cat_state = ""
+        logger.debug("Edited cat list: "
+                     + str([cat.label for cat in edited_cat_list])
+                     + " - Original cat list: "
+                     + str([cat.label for cat in original_cat_list]))
+        for category in edited_cat_list:
+            if category.cat_id not in [cat.cat_id for
+                                       cat in original_cat_list]:
+                cat_state = "created"
+            else:
+                cat_state = "modified"
+            cat_list.append({"cat_label": category.label,
+                             "cat_description": category.description,
+                             "cat_id": category.cat_id,
+                             "cat_properties": category.properties,
+                             "state": cat_state})
+    return cat_list
+
+@module.route('/<string:repository>/workshop',
+           defaults={'deleted_cat_id': None, 'deleted_changes_id': None},
+           methods=['GET', 'POST'])
+@module.route('/<string:repository>/workshop/delete-modifs-<deleted_changes_id>',
+           defaults={'deleted_cat_id': None},
+           methods=['POST'])
+@module.route('/<string:repository>/workshop/delete-<deleted_cat_id>',
+           defaults={'deleted_changes_id': None},
+           methods=['POST'])
+def workshop(repository, deleted_cat_id, deleted_changes_id):
+    """
+        View that has a list of all categories available. Template is
+        categories/workshop.html, located in src/templates/
+
+        Note: it also handles category deletion from the same page.
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    cat_api_instance = CategoryAPI()
+    cat_changes_api_instance = CategoryChangesAPI()
+
+    if request.method == "POST":
+        if deleted_changes_id is None and deleted_cat_id is None:
+            cat_changes_api_instance.delete(repository=repository)
+        elif deleted_changes_id is not None:
+            logger.debug("Changes for category "
+                         + deleted_changes_id
+                         + " will be deleted.")
+            cat_changes_api_instance.delete(
+                repository=repository,
+                modified_cat_id=deleted_changes_id
+            )
+        # We identify if we want to delete a category
+        elif deleted_cat_id is not None:
+            logger.debug("Category "
+                         + deleted_cat_id
+                         + " will be deleted.")
+            cat_api_instance.delete(
+                repository=repository,
+                deleted_cat_id=deleted_cat_id
+            )
+        return redirect(url_for('categories.workshop', repository=repository))
+    elif request.method == "GET":
+
+        cat_list = make_category_list(repository=repository)
+            # logger.debug(c.properties)
+        cat_list = sorted(cat_list, key=lambda cat: cat['cat_id'])
+        return render_template('categories/workshop.html',
+                               cat_list=cat_list,
+                               current_repository=repository)
+
+
+class CommitForm(Form):
+    """
+        Custom form class for commiting changes
+    """
+    commit_message = StringField(
+        "Message de soumission (obligatoire)",
+        validators=[DataRequired()]
+    )
+
+
+@module.route('/<string:repository>/'
+              + 'submit/'
+              + 'delete-changes-<deleted_changes_id>',
+           defaults={'deleted_cat_id': None},
+           methods=['POST'])
+@module.route('/<string:repository>/submit/delete-<deleted_cat_id>',
+           defaults={'deleted_changes_id': None},
+           methods=['POST'])
+@module.route('/<string:repository>/submit',
+           defaults={'deleted_cat_id': None, 'deleted_changes_id': None},
+           methods=['GET', 'POST'])
+def submit(repository, deleted_cat_id, deleted_changes_id):
+    """
+        View that handles all the visualisation of changes for a user's
+        session, links to the editor forms, allows the users to cancel their
+        own changes and submits all their changes.
+
+        Template is categories/submit.html, located in src/templates/
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    cat_api_instance = CategoryAPI()
+    cat_changes_api_instance = CategoryChangesAPI()
+
+    logger.debug(
+        "submit page for " + repository + ", method is " + request.method
+    )
+
+    if deleted_cat_id is None and deleted_changes_id is None:
+
+        commit_form = CommitForm(request.form)
+
+        # If it's a POST with no delete_cat_id and delete_modifs_id, then we
+        # will submit all the stored changes
+        if request.method == "POST":
+            if commit_form.validate_on_submit():
+                cat_api_instance.put(repository=repository)
+                cat_changes_api_instance.delete(repository=repository)
+            return redirect(url_for('categories.workshop', repository=repository))
+
+        # if it's a GET with no delete_cat_id or deleted_changes_id, then we'll
+        # display the changes
+        elif request.method == "GET":
+            cat_list = make_category_list(repository=repository)
+            original_cat_list = make_category_list(
+                repository=repository,
+                without_changes=True
+            )
+
+            created_cat_count = len([
+                created_cat for created_cat in cat_list
+                if created_cat["state"] == "created"
+            ])
+            edited_cat_count = len([
+                edited_cat for edited_cat in cat_list
+                if edited_cat["state"] == "modified"
+            ])
+            deleted_cat_count = len([
+                deleted_cat for deleted_cat in cat_list
+                if deleted_cat["state"] == "deleted"
+            ])
+
+            return render_template('categories/submit.html',
+                                   cat_list=cat_list,
+                                   original_cat_list=original_cat_list,
+                                   original_cat_count=len(original_cat_list),
+                                   created_cat_count=created_cat_count,
+                                   edited_cat_count=edited_cat_count,
+                                   deleted_cat_count=deleted_cat_count,
+                                   commit_form=commit_form,
+                                   current_repository=repository)
+
+    # One of the ids is not None (either deleting a category or a modification)
+    else:
+        # We only do that if we have a POST
+        if (request.method == "POST"):
+            # We identify if we want to delete a change
+            if deleted_changes_id is not None:
+                cat_changes_api_instance.delete(
+                    repository=repository,
+                    modified_cat_id=deleted_changes_id
+                )
+            # We identify if we want to delete a category
+            elif deleted_cat_id is not None:
+                cat_api_instance.delete(
+                    repository=repository,
+                    deleted_cat_id=deleted_cat_id
+                )
+            return redirect(
+                url_for('categories.submit', repository=repository)
+            )
+        else:
+            return redirect(
+                url_for('home.index')
+            )
+
+
+
+class PropertyForm(Form):
+    """
+        Form of a given property, each one is a couple of hidden fields that
+        can be Javascript-generated in the template
+    """
+    property_predicate = HiddenField(
+        validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())]
+    )
+    property_object = HiddenField(
+        validators=[DataRequired()]
+    )
+
+
+class CategoryForm(Form):
+    """
+        Custom form class for creating a category with the absolute minimal
+        attributes (label and description)
+    """
+    label = StringField(
+        "Nom de la categorie (obligatoire)",
+        validators=[DataRequired()]
+    )
+    description = TextAreaField(
+        "Description de la categorie (obligatoire)",
+        validators=[DataRequired()]
+    )
+    properties = FieldList(FormField(PropertyForm), validators=[Optional()])
+
+
+@module.route('/<string:repository>/editor',
+              defaults={'cat_id': None},
+              methods=['GET', 'POST'])
+@module.route('/<string:repository>/editor/<string:cat_id>',
+              methods=['GET', 'POST'])
+def editor(repository, cat_id):
+    """
+        View that handles creation and edition of categories. Template is
+        categories/editor.html, located in src/templates
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    cat_api_instance = CategoryAPI()
+    cat_changes_api_instance = CategoryChangesAPI()
+
+    specific_serialized_cat = ""
+    # Serialization of the category of id cat_id, either from
+    # CategoryChangesAPI (if it was modified) or from CategoryAPI (if it
+    # was not)
+
+    changes_response = None
+    # Changes on the category cat_id we get from CategoryChangesAPI
+
+    current_cat = None
+    current_cat_id = None
+    current_cat_properties = []
+    # Args for the template, if we create a new category they are not used
+
+    cat_list = []
+    # Category list that will be used by the template
+    deleted_cat_dict = {}
+    # Deleted categories we won't append to cat_list
+    modified_cat_dict = {}
+    # Modified categories to append to cat_list in case label changed
+    serialized_cat_list = []
+    # Existing categories we get from CategoryAPI
+    cat_changes = {}
+    # Changes list we get from CategoryChangesAPI
+
+    if cat_id is not None:
+        changes_response = cat_changes_api_instance.get(
+            repository=repository,
+            modified_cat_id=cat_id
+        )
+        if changes_response[0]["type"] is "deleted":
+            abort(404)
+        elif changes_response[0]["type"] is "modified":
+            specific_serialized_cat = changes_response[0] \
+                                                      ["category"] \
+                                                      [cat_id]
+        elif changes_response[0]["type"] is "untouched":
+            category_api_response = cat_api_instance.get(
+                repository=repository,
+                cat_id=cat_id
+            )
+            if category_api_response == 404:
+                abort(404)
+            else:
+                specific_serialized_cat = cat_api_response[0]
+        logger.debug(specific_serialized_cat)
+
+        cat_rdf_graph = Graph()
+        cat_rdf_graph.parse(source=StringIO(specific_serialized_cat),
+                            format='turtle')
+
+        current_cat = Category(graph=cat_rdf_graph)
+        current_cat_id = current_cat.cat_id
+        current_cat_properties = current_cat.properties
+        logger.debug(current_cat.properties)
+
+    serialized_cat_list = cat_api_instance.get(repository)[0]
+    cat_changes = cat_changes_api_instance.get(repository)[0]
+    deleted_cat_dict = cat_changes["deleted_categories"]
+    modified_cat_dict = cat_changes["modified_categories"]
+    logger.debug(changes_response)
+
+    for modified_cat_name in modified_cat_dict.keys():
+        modified_cat_rdf_graph = Graph()
+        modified_cat_rdf_graph.parse(
+            source=StringIO(modified_cat_dict[modified_cat_name]),
+            format='turtle'
+        )
+        modified_cat = Category(graph=modified_cat_rdf_graph)
+        cat_list.append({"cat_label": modified_cat.label,
+                         "cat_description": modified_cat.description,
+                         "cat_id": modified_cat.cat_id,
+                         "cat_properties": modified_cat.properties})
+
+    for serialized_cat in serialized_cat_list:
+        cat_rdf_graph = Graph()
+        cat_rdf_graph.parse(source=StringIO(serialized_cat),
+                            format='turtle')
+        cat = Category(graph=cat_rdf_graph)
+        if cat.cat_id not in deleted_cat_dict.keys() and \
+           cat.cat_id not in modified_cat_dict.keys():
+            cat_list.append({"cat_label": cat.label,
+                             "cat_description": cat.description,
+                             "cat_id": cat.cat_id,
+                             "cat_properties": cat.properties})
+
+    cat_form = CategoryForm(request.form)
+
+    if request.method == "GET":
+        """
+            GET Method means we will display the editor
+        """
+        if current_cat is not None:
+            cat_form.label.data = current_cat.label
+            cat_form.description.data = current_cat.description
+            for (cat_predicate, cat_object) in current_cat_properties:
+                if app.config["PROPERTY_LIST"] \
+                             [cat_predicate] \
+                             ["object_type"] == "uriref-category":
+                    namespace, object_id = cat_object.split("#",1)
+                else:
+                    object_id = cat_object
+                cat_form.properties.append_entry({
+                    "property_predicate": cat_predicate,
+                    "property_object": object_id
+                })
+        # list of category that will be used in property editor, list of dict
+        # {"cat_id": cat.cat_id,
+        #  "cat_label": cat.label,
+        #  "cat_description": cat.description,
+        #  "cat_properties": cat.properties}
+        logger.debug("category list that can be linked :" + str(cat_list))
+        logger.debug(
+            "deleted categories list that can't be linked :"
+            + str(list(deleted_cat_dict.keys()))
+        )
+        return render_template('categories/editor.html',
+                               cat_id=current_cat_id,
+                               cat_properties=current_cat_properties,
+                               form=cat_form,
+                               cat_list=cat_list,
+                               deleted_cat_list=list(deleted_cat_dict.keys()),
+                               current_repository=repository)
+    elif request.method == "POST":
+        """
+            POST Method means we will compute the form data, call the relevant
+            "POST" and "PUT" method on CategoryAPI, and return to a display
+            page (workshop for now, ideally the one from whence we came)
+        """
+        if cat_form.validate_on_submit():
+            cat_data = {}
+            cat_data["label"] = cat_form.label.data
+            cat_data["description"] = cat_form.description.data
+            cat_data["properties"] = [
+                (cat_property["property_predicate"],
+                 cat_property["property_object"])
+                for cat_property in cat_form.properties.data
+            ]
+
+            if cat_id is not None:
+                logger.debug(str(cat_data))
+                cat_api_instance.put(
+                    repository=repository,
+                    cat_id=cat_id,
+                    cat_data=cat_data
+                )
+            else:
+                logger.debug(str(cat_data))
+                cat_api_instance.post(
+                    repository=repository,
+                    cat_data=cat_data
+                )
+            return redirect(url_for('categories.workshop', repository=repository))
+        else:
+            # if form doesn't validate we don't want to delete whatever
+            # changes the user did
+            return render_template('categories/editor.html',
+                                   cat_id=cat_id,
+                                   cat_properties=current_cat_properties,
+                                   form=cat_form,
+                                   cat_list=cat_list,
+                                   deleted_cat_list=list(
+                                       deleted_cat_dict.keys()
+                                   ),
+                                   current_repository=repository)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/views/home.py	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,214 @@
+"""
+home.py:
+The views functions that handle authentication and index pages
+"""
+
+from catedit import app, github
+from views.utils import check_user_status
+from requests import get
+from requests.auth import HTTPBasicAuth
+from flask import render_template, request, redirect, url_for, \
+                  session, Blueprint
+from flask.ext.github import GitHubError
+from flask_wtf import Form
+from wtforms import StringField, PasswordField
+from wtforms.validators import DataRequired
+
+module = Blueprint('home', __name__)
+logger = app.logger
+
+
+@module.route('/', methods=['GET'])
+@module.route('/index', methods=['GET'])
+def index():
+    """
+        View for index page (for now it's only a readme text so no computing
+        is required)
+    """
+    return render_template("home/index.html")
+
+
+class LoginForm(Form):
+    """
+        Custom form class for commiting changes
+    """
+    user_login = StringField(
+        "Nom d'utilisateur Github",
+        validators=[DataRequired()]
+    )
+    user_password = PasswordField(
+        "Mot de passe Github",
+        validators=[DataRequired()]
+    )
+
+
+@module.route('/catedit-login', methods=["GET", "POST"])
+def login():
+    """
+        Function that manages authentication (Github), login
+
+        Note: If Persistence is set to PersistenceToFile (categories stored
+        in local files, used for debugging), creates a mock user named
+        "FileEditUser"
+    """
+    if not session.get("user_logged", False):
+        session["modified_categories"] = {
+            repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
+                                           ["REPOSITORY_LIST"]
+        }
+        session["deleted_categories"] = {
+            repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
+                                           ["REPOSITORY_LIST"]
+        }
+        if app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToGithub":
+            login_form = LoginForm(request.form)
+            if request.method == "GET":
+                # We'll render the login form
+                return render_template(
+                    "home/login.html",
+                    form=login_form,
+                )
+            elif request.method == "POST":
+                if login_form.validate_on_submit():
+                    # We'll try to get the auth token for given username
+                    try:
+                        auth_response = get(
+                            "https://api.github.com/"
+                            + "authorizations",
+                            auth=HTTPBasicAuth(
+                                login_form.user_login.data,
+                                login_form.user_password.data
+                            )
+                        )
+                        for auth in auth_response.json():
+                            if auth["app"]["client_id"] \
+                            == app.config["GITHUB_CLIENT_ID"]:
+                                session["user_code"] = auth["token"]
+                                session["user_logged"] = True
+                    except:
+                        logger.debug(
+                            "Error requesting authorizations for"
+                            + " user. Either the user is new to catedit, or "
+                            + "entered a wrong username/password"
+                        )
+                    logger.debug(str(github.get("rate_limit")["resources"]))
+                    logger.debug(
+                        "user token found by request: "
+                        + str(session.get("user_code", None))
+                    )
+                    if session.get("user_code", None) == None:
+                        # We didn't get it, so we direct the user to the login page
+                        # with a link to github oauth system
+                        return render_template(
+                            "home/login.html",
+                            form=login_form
+                        )
+                    else:
+                        # we did get it, so we redirect to callback function
+                        # to wrap up user auth
+                        return redirect(url_for('home.login_callback'))
+                else:
+                    # form didn't validate, so we send it back to user
+                    return render_template(
+                        "home/login.html",
+                        form = login_form
+                    )
+        elif app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToFile":
+            session["user_logged"] = True
+            session["user_can_edit"] = {}
+            session["user_can_edit"]["local"] = True
+            session["user_login"] = "FileEditUser"
+            return redirect(url_for('home.index'))
+    else:
+        return redirect(url_for('home.index'))
+
+@module.route('/catedit-login-confirm', methods=["GET", "POST"])
+def login_confirm():
+    """
+        Function called if the user is new or revoked the auth token
+    """
+    if not session.get("user_logged", False):
+        if request.method == "POST":
+            return github.authorize(
+                scope="repo",
+                redirect_uri=url_for('home.login_callback', _external=True)
+            )
+    else:
+        return redirect(url_for('home.index'))
+
+@module.route('/catedit-callback')
+@github.authorized_handler
+def login_callback(oauth_code):
+    """
+        Function that handles callback from Github after succesful login
+    """
+    session.permanent = False
+    if session.get("user_code", None) == None:
+    # That means we got here using github callback and not the login form
+        session["user_code"] = oauth_code
+    logger.debug(session["user_code"])
+    session["user_logged"] = True
+    session["user_login"] = "auth-error"
+    try:
+        logger.debug(
+            "after login: "
+            + str(github.get("rate_limit")["resources"])
+        )
+        session["user_login"] = github.get("user")["login"]
+    except GitHubError as ghe:
+        logger.error(
+            "GitHubError trying to get the user login"
+        )
+        logger.error(ghe.response.text)
+    try:
+        repo_list = []
+        repo_list = github.get("user/repos")
+        logger.debug(str(github.get("rate_limit")["resources"]))
+        for repo in repo_list:
+            logger.debug(repo["name"])
+        user_repos_name = [repo["name"] for repo in repo_list]
+        logger.debug(
+            str(user_repos_name) + " "
+            + str(app.config["PERSISTENCE_CONFIG"]
+                            ["REPOSITORY_LIST"])
+        )
+        session["user_repositories"] = list(
+            set(user_repos_name).intersection(
+                app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]
+            )
+        )
+        session["user_can_edit"] = {}
+        for repo in session["user_repositories"]:
+            if repo in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+                session["user_can_edit"][repo] = True
+        logger.debug(session["user_can_edit"])
+    except GitHubError as ghe:
+        logger.error(
+            "GitHubError trying to get the list of repository for user "
+            + session["user_login"]
+        )
+        logger.error(ghe.response.text)
+    return redirect(url_for('home.index'))
+
+@module.route('/catedit-logout')
+def logout():
+    """
+        Function that manages authentication (Github), logout
+
+        Note: if you want to switch github users, you will have to logout of
+        Github, else when logging back in, github will send the app the
+        same oauth code
+    """
+    session["user_logged"] = None
+    session["user_code"] = None
+    session["user_login"] = None
+    session["user_can_edit"] = None
+    session["modified_categories"] = {
+        repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
+                                       ["REPOSITORY_LIST"]
+    }
+    session["deleted_categories"] = {
+        repo: {} for repo in app.config["PERSISTENCE_CONFIG"]
+                                       ["REPOSITORY_LIST"]
+    }
+    return redirect(url_for('home.index'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/views/social.py	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,184 @@
+"""
+social.py:
+The views functions that handle comment display and posting
+"""
+
+from catedit import app, github, cache
+from views.utils import check_user_status, get_comments, \
+                                post_comment, get_commits, get_issues, \
+                                convert_github_date, get_category_list
+from flask import render_template, request, redirect, url_for, \
+                  session, Blueprint
+from flask.ext.github import GitHubError
+from flask_wtf import Form
+from wtforms import TextAreaField, StringField
+from wtforms.validators import DataRequired
+
+module = Blueprint('social', __name__)
+logger = app.logger
+
+@module.route("/<string:repository>/social",
+              methods=["GET"])
+def index(repository):
+    """
+        View that displays every changeset and thread in general discussion
+        and links to every thread
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    issues_data = []
+    commits_data = []
+
+    changeset_list = get_commits(repository)
+    discussion_list = get_issues(repository)
+
+    return render_template(
+        "social/index.html",
+        current_repository=repository,
+        discussion_list=discussion_list,
+        changeset_list=changeset_list
+    )
+
+class CommentForm(Form):
+    """
+        Custom form class for commiting changes
+    """
+    comment_field = TextAreaField(
+        "Poster un commentaire: ",
+        validators=[DataRequired()]
+    )
+
+
+@module.route("/<string:repository>/changesets/<string:changeset_id>",
+              methods=["GET", "POST"])
+def changeset(repository, changeset_id):
+    """
+        View that displays a snapshot of the repository as it was for a given
+        changeset, and the related discussion to this changeset. Allows
+        users to comment on it.
+
+        For re-building the old category set we use the tree and blob data:
+        1) first we get the commit of id changeset_id
+        2) we get the tree associated with this commit
+        3) we get all the blobs referenced by this tree
+        4) we build categories from the blobs contents
+        --> Will probably be incorporated into a function later on
+
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    comment_form = CommentForm()
+    comments_list = get_comments({
+        "repository": repository,
+        "type": "commits",
+        "id": changeset_id
+    })
+    cat_list = get_category_list(repository, changeset_id)
+
+    if request.method == "GET":
+        return render_template(
+            "social/changeset.html",
+            cat_list=cat_list,
+            comments=comments_list,
+            changeset_id = changeset_id,
+            comment_form = comment_form,
+            current_repository=repository
+        )
+    elif request.method == "POST":
+        if comment_form.validate_on_submit():
+            comment_data = {
+                "repository": repository,
+                "type": "commits",
+                "thread_id": changeset_id,
+                "comment_body": comment_form.comment_field.data
+            }
+            return_id = post_comment(comment_data)
+            return redirect(url_for(
+                "social.changeset",
+                repository=repository,
+                changeset_id = return_id
+            ))
+        else :
+            return render_template(
+                "social/changeset.html",
+                cat_list=cat_list,
+                comments=comments_list,
+                changeset_id = changeset_id,
+                comment_form = comment_form,
+                current_repository=repository
+            )
+
+class NewDiscussionForm(Form):
+    """
+        Custom form class for commiting changes
+    """
+    discussion_title = StringField(
+        "Titre de la discussion: ",
+        validators=[DataRequired()]
+    )
+    comment_field = TextAreaField(
+        "Commentaire d'ouverture (facultatif): "
+    )
+
+@module.route("/<string:repository>/discussions/<string:discussion_id>",
+              methods=["GET", "POST"])
+def discussion(repository, discussion_id):
+    """
+        View that displays the discussion of a given id and allows users
+        to post comments on it.
+    """
+    if repository not in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]:
+        abort(404)
+
+    check_user_status(repository)
+
+    comment_form = None
+    if discussion_id == "new":
+        comment_form = NewDiscussionForm()
+        comments_list = []
+    else:
+        comment_form = CommentForm()
+        comments_list = get_comments({
+            "repository": repository,
+            "type": "issues",
+            "id": discussion_id
+        })
+
+    if request.method == "GET":
+        return render_template(
+            "social/discussion.html",
+            comments=comments_list,
+            comment_form=comment_form,
+            current_repository=repository,
+            discussion_id=discussion_id
+        )
+    elif request.method == "POST":
+        if comment_form.validate_on_submit():
+            comment_data = {
+                "repository": repository,
+                "type": "issues",
+                "thread_id": discussion_id,
+                "comment_body": comment_form.comment_field.data
+            }
+            if discussion_id == "new":
+                comment_data["thread_title"] = comment_form.discussion_title.data
+            return_id = post_comment(comment_data)
+            return redirect(url_for(
+                "social.discussion",
+                repository=repository,
+                discussion_id=return_id
+            ))
+        else:
+            return render_template(
+                "social/discussion.html",
+                comments=comments_list,
+                comment_form=comment_form,
+                current_repository=repository,
+                discussion_id=discussion_id
+            )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/views/utils.py	Fri Feb 13 17:22:21 2015 +0100
@@ -0,0 +1,365 @@
+"""
+utils.py:
+Module that groups utility functions that are used by views, partly because
+most of them do requests to the Github API and as such must be cached
+"""
+
+from catedit import app, github, cache
+from catedit.models import Category
+from flask import redirect, url_for, session, abort
+from flask.ext.github import GitHubError
+from datetime import datetime
+from rdflib import Graph
+from base64 import b64decode
+from io import StringIO
+
+logger = app.logger
+
+def check_user_status(repository):
+    """
+        Function to call at the beginning of every view that would require
+        authentication and editing privilege to work properly (basically
+        everything save login and index page)
+    """
+    if not session.get("user_logged", None):
+        return redirect(url_for("home.index"))
+    if not session.get("user_can_edit", {}).get(repository, False):
+        return redirect(url_for("home.index"))
+
+@cache.memoize(timeout=3600)
+def get_comments(request_data):
+    """
+        Function that takes a dict with the following two values:
+            * type: either "issues" or "commits"
+            * id: the id of the issue of commit to get comments from
+        then builds a comment list with each comment being a dict with the
+        following format:
+        {
+            "author": author of the comment
+            "body": body of the comment
+            "date": date of the comment format dd/mm/yy hh:mm
+        }
+    """
+    github_comments_data = []
+
+    if request_data["type"] == "commits":
+        commits_list = get_commits(request_data["repository"])
+        if request_data["id"] not in [commit["id"] for commit in commits_list]:
+            abort(404)
+    elif request_data["type"] == "issues":
+        issues_list = get_issues(request_data["repository"])
+        if request_data["id"] not in [issue["id"] for issue in issues_list]:
+            abort(404)
+
+    try:
+        github_comments_data = github.get(
+            "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + request_data["repository"] + "/"
+            + request_data["type"] + "/"
+            + request_data["id"] + "/comments?per_page=100"
+        )
+
+    except GitHubError as ghe:
+        logger.error(
+            "Error trying to get comments with following data: "
+            + str(request_data)
+        )
+        logger.error(ghe.response.text)
+
+    comment_list = []
+    for comment in github_comments_data:
+        comment_dict = {
+            "author": comment["user"]["login"],
+            "body": comment["body"],
+            "date": convert_github_date(
+                comment["created_at"]
+            )
+        }
+        comment_list.append(comment_dict)
+
+    discussion_data = {}
+    try:
+        discussion_data = github.get(
+            "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + request_data["repository"] + "/"
+            + request_data["type"] + "/"
+            + request_data["id"]
+        )
+    except GitHubError as ghe:
+        logger.error(
+            "Error trying to get the or issue of id " + request_data["id"]
+        )
+        logger.error(
+            "endpoint: " + "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + request_data["repository"] + "/"
+            + request_data["type"] + "/"
+            + request_data["id"]
+        )
+        logger.error(ghe.response.text)
+
+    thread_author = ""
+    thread_opening_date = ""
+    thread_title = ""
+    thread_opening_post = ""
+
+    if request_data["type"] == "commits":
+        thread_author = discussion_data.get("author",{}).get("login", "")
+        thread_opening_date = convert_github_date(
+            discussion_data.get(
+                "commit",
+                {}
+            ).get(
+                "author",
+                {}
+            ).get("date","")
+        )
+        thread_title = discussion_data.get("commit",{}).get("message","")
+    elif request_data["type"] == "issues":
+        thread_author = discussion_data.get("user",{}).get("login", "")
+        thread_opening_date = convert_github_date(
+            discussion_data.get("created_at", "0001-01-01T00:00:00Z")
+        )
+        thread_title = discussion_data.get("title", "")
+        thread_opening_post = discussion_data.get("body", "")
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+    thread_dict = {
+        "author": thread_author,
+        "title": thread_title,
+        "opening_date": thread_opening_date,
+        "comment_list": comment_list,
+        "opening_post": thread_opening_post
+    }
+    return thread_dict
+
+
+def post_comment(request_data):
+    """
+        Function that posts a given comment to github.
+
+        * repository is the repository to post in
+        * type is either "issues" or "commits"
+        * thread_id is the id of the issue or the commit to comment
+        * thread_title is the title of the new issue, if we're posting a new
+        issue
+        * comment_body is the content of the comment
+    """
+    comment_data = {
+        "body": request_data["comment_body"]
+    }
+    return_id = ""
+    if request_data["thread_id"] != "new":
+        try:
+            github_response = github.post(
+                "repos/"
+                + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+                + request_data["repository"] + "/"
+                + request_data["type"] + "/"
+                + request_data["thread_id"]
+                + "/comments",
+                data=comment_data
+            )
+            return_id = request_data["thread_id"]
+        except GitHubError as ghe:
+            logger.error(
+                "Error posting comment with following data: "
+                + str(request_data)
+            )
+            logger.error(ghe.response.text)
+    else:
+        # We're posting a new issue
+        comment_data["title"] = request_data["thread_title"]
+        try:
+            github_response = github.post(
+                "repos/"
+                + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+                + request_data["repository"] + "/"
+                + request_data["type"],
+                data=comment_data
+            )
+            return_id = str(github_response["number"])
+        except GitHubError as ghe:
+            logger.error(
+                "Error posting new issue with following data: "
+                + str(request_data)
+            )
+            logger.error(ghe.response.text)
+    cache.clear()
+    return return_id
+
+@cache.memoize(timeout=3600)
+def get_commits(repository):
+    """
+        Fuction that get the list of commits for a given repository. Returns a
+        list of dict with the format:
+        {
+            id : commit id
+            title : commit message
+            author : commit author
+            comment_count : commit comments count
+        }
+    """
+    commits_data = []
+    try:
+        commits_data = github.get(
+            "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + repository
+            + "/commits?per_page=100"
+        )
+    except GitHubError as ghe:
+        logger.error("Error getting commits for repo " + repository)
+        logger.error(ghe.response.text)
+    changeset_list = [
+        {
+            "id": commit["sha"],
+            "title": commit["commit"]["message"],
+            "date": convert_github_date(
+                commit["commit"]["committer"]["date"],
+            ),
+            "author": commit["commit"]["committer"]["name"],
+            "comment_count": commit["commit"]["comment_count"],
+        }
+        for commit in commits_data
+    ]
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+    return changeset_list
+
+@cache.memoize(timeout=3600)
+def get_issues(repository):
+    """
+        Fuction that get the list of issues for a given repository. Returns a
+        list of dict with the format:
+        {
+            id: issue id
+            title: issue title
+            author: issue author
+            opening_date: issue opening date
+            last_updated: last update date
+            comment_count: comments count
+        }
+    """
+    issues_data = []
+    try:
+        issues_data = github.get(
+        "repos/"
+        + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+        + repository
+        + "/issues?per_page=100")
+    except GitHubError as ghe:
+        logger.error("Error getting issues for repo " + repository)
+        logger.error(ghe.response.text)
+
+    discussion_list = [
+        {
+            "id": str(issue["number"]),
+            "title": issue["title"],
+            "author": issue["user"]["login"],
+            "opening_date": convert_github_date(issue["created_at"]),
+            "last_updated": convert_github_date(issue["updated_at"]),
+            "comment_count": issue["comments"],
+        }
+        for issue in issues_data
+    ]
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+    return discussion_list
+
+@cache.memoize(timeout=3600)
+def get_category_list(repository, changeset_id):
+    """
+        Get the category list as it was following the changeset of
+        id changeset_id
+    """
+    commits_list = get_commits(repository)
+    if changeset_id not in [commit["id"] for commit in commits_list]:
+        abort(404)
+
+    # First step
+    commit_data = {}
+    try:
+        commit_data = github.get(
+            "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + repository + "/commits/"
+            + changeset_id
+        )
+    except GitHubError as ghe:
+        logger.error("Error trying to get the commit of id " + changeset_id)
+        logger.error(ghe.response.text)
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+
+    tree_sha = commit_data.get("commit", {}).get("tree", {}).get("sha", "")
+
+    # Second step
+    tree_data = {}
+    try:
+        tree_data = github.get(
+            "repos/"
+            + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/"
+            + repository + "/git/trees/"
+            + commit_data["commit"]["tree"]["sha"]
+            + "?recursive=1"
+        )
+    except GitHubError as ghe:
+        logger.error("Error trying to get the tree of sha " + tree_sha)
+        logger.error(ghe.response.text)
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+    logger.debug(tree_data)
+
+    # Third step and fourth step
+    cat_list = []
+    for blob in tree_data.get("tree", []):
+        if app.config["PERSISTENCE_CONFIG"]["CATEGORIES_PATH"] in blob["path"]:
+            blob_data = {}
+            try:
+                blob_data = github.get(
+                    "repos/"
+                    + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"]
+                    + "/" + repository + "/git/blobs/"
+                    + blob["sha"]
+                )
+            except GitHubError as ghe:
+                logger.error(
+                    "Error trying to get the blob of sha " + blob["sha"]
+                )
+                logger.error(ghe.response.text)
+
+            cat_graph = Graph()
+            cat_graph.parse(
+                source=StringIO(
+                    str(b64decode(blob_data["content"]), "utf-8")
+                ),
+                format="turtle"
+            )
+            category = Category(graph=cat_graph)
+            cat_list.append(
+                {
+                    "cat_label": category.label,
+                    "cat_description": category.description,
+                    "cat_id": category.cat_id,
+                    "cat_properties": category.properties,
+                    "state": "original"
+                }
+            )
+
+    logger.debug(str(github.get("rate_limit")["resources"]))
+    return cat_list
+
+def convert_github_date(date):
+    """
+        Function that converts github date format to a
+        "dd/mm/yyyy à hh:mm" format
+    """
+    return datetime.strptime(
+        date,
+        "%Y-%m-%dT%H:%M:%SZ"
+    ).strftime(
+        "%d/%m/%Y à %H:%M"
+    )
--- a/virtualenv/requirements.txt	Fri Jan 30 12:41:40 2015 +0100
+++ b/virtualenv/requirements.txt	Fri Feb 13 17:22:21 2015 +0100
@@ -12,6 +12,7 @@
 isodate==0.5.1
 itsdangerous==0.24
 pep8==1.5.7
+pylint
 pyparsing==2.0.3
 python-slugify==0.1.0
 pytz==2014.10