Pylint + pep8 + adapted code to Python 3 + added support for authentication when persistence is set to PersistenceToFile + cleaning up settings.py/config.py.tmpl and updated readme
authorNicolas DURAND <nicolas.durand@iri.centrepompidou.fr>
Wed, 17 Dec 2014 17:21:06 +0100
changeset 12 8ca7be41e3ca
parent 11 f2d54d2841f3
child 13 ea5f985156b1
Pylint + pep8 + adapted code to Python 3 + added support for authentication when persistence is set to PersistenceToFile + cleaning up settings.py/config.py.tmpl and updated readme
.hgignore
Readme.md
dev/modules/sysconfig/manifests/packages.pp
dev/modules/sysconfig/manifests/virtualenv.pp
src/catedit/__init__.py
src/catedit/api.py
src/catedit/config.py.tmpl
src/catedit/main.py
src/catedit/models.py
src/catedit/persistence.py
src/catedit/settings.py
src/catedit/templates/cateditor.html
src/catedit/templates/catrecap.html
src/catedit/views.py
virtualenv/requirements.txt
--- a/.hgignore	Tue Dec 16 11:14:55 2014 +0100
+++ b/.hgignore	Wed Dec 17 17:21:06 2014 +0100
@@ -4,3 +4,5 @@
 *.pyc
 src/catedit/config.py
 src/catedit/log/
+run/log/
+run/files/
\ No newline at end of file
--- a/Readme.md	Tue Dec 16 11:14:55 2014 +0100
+++ b/Readme.md	Wed Dec 17 17:21:06 2014 +0100
@@ -28,7 +28,11 @@
 
 * HOST : The host on which the app will run, default is "0.0.0.0" for development purpose as "localhost" won't work with vagrant and windows.
 * LOGGING : Wether or not the app write log files
+* LOGGING_LEVEL : if LOGGING is True, indicates what logging level will be used
 * DEBUG : Wether or not the app is on debug mode. If True, then each modification of a given file will restart the server allowing for easy development.
+* SECRET_KEY : Secret key to secure WTForms
+* PERSISTENCE_METHOD : What Persistence method will be used. Currently, either "PersistenceToFile" or "PersistenceToGithub"
+* FILE_SAVE_DIRECTORY : If using PersistenceToFile, directory where the turtle files will be saved locally
 * REPOSITORY_NAME : The name of the repository your app will use to store categories
 * REPOSITORY_OWNER : The name of the owner of the repository (typically a "admin" user)
 * CATEGORIES_PATH : Where on the repository categories will be stored. Default to /categories
@@ -44,7 +48,7 @@
 
 ## Additional/Advanced informations ##
 
-** Changing the property list : ** If you want to change the property list available to users editing/creating categories, you have to put the following entry in config.py
+** Changing the property list : ** If you want to change the property list available to users editing/creating categories, you have to edit the following entry in config.py
 
 * PROPERTY_LIST : My list of properties ...
 
--- a/dev/modules/sysconfig/manifests/packages.pp	Tue Dec 16 11:14:55 2014 +0100
+++ b/dev/modules/sysconfig/manifests/packages.pp	Wed Dec 17 17:21:06 2014 +0100
@@ -7,13 +7,14 @@
       'libjpeg8-dev',
       'libxslt-dev',
       'libxml2',
-      'mercurial'
+      'mercurial',
+      'pylint'
   ]
-  
+
   package { $catedit_pkgs: ensure => "installed" }
 
   #upgrade setuptools
   exec { '/usr/bin/easy_install --upgrade setuptools': require => Package[$catedit_pkgs]}
 
-  
+
 }
--- a/dev/modules/sysconfig/manifests/virtualenv.pp	Tue Dec 16 11:14:55 2014 +0100
+++ b/dev/modules/sysconfig/manifests/virtualenv.pp	Wed Dec 17 17:21:06 2014 +0100
@@ -15,7 +15,7 @@
              provider => 'shell',
              require     => Exec['easy_install_pip'];
              'create_virtualenv':
-             command     => "virtualenv ${path_to_virtualenv}",
+             command     => "virtualenv -p `which python3.4` ${path_to_virtualenv}",
              timeout     => 2400,
              returns     => [ 0, 100 ],
              provider => 'shell',
--- a/src/catedit/__init__.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/__init__.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,15 +1,19 @@
+"""
+__init__.py:
+module main file used to configure the Flask app
+"""
 from flask import Flask, session
 from flask.ext.github import GitHub
 from flask.ext.cache import Cache
-from settings import *
-from config import *
-from logging import *
+from settings import AppSettings
+from config import AppConfig
+from logging import FileHandler, Formatter
 
 # set up app and database
 app = Flask(__name__)
-app.config.from_object(appSettings)
+app.config.from_object(AppSettings)
 cache = Cache(app, config={"CACHE_TYPE": "simple"})
-app.config.from_object(appConfig)
+app.config.from_object(AppConfig)
 
 github = GitHub(app)
 
--- a/src/catedit/api.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/api.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,82 +1,101 @@
-from flask.ext.restful import Resource, fields, Api, reqparse
+"""
+api.py:
+contains the api that links the views (views.py) to the model (models.py) and
+its persistence method (persistence.py). As it only trades rdf graphs
+serializations strings, it isn't bound to one specific view
+"""
+
+from flask.ext.restful import Resource, Api, reqparse
 from flask import request
 from catedit import app, cache
-from models import Category, CategoryManager
-from rdflib import Graph, RDF
-from utils import *
-from settings import *
-from config import *
-import os
+from catedit.models import Category, CategoryManager
 
 
 api = Api(app)
-logger = app.logger
+
+LOGGER = app.logger
 
-cat_parser = reqparse.RequestParser()
-cat_parser.add_argument('label', type=str)
-cat_parser.add_argument('description', type=str)
-cat_parser.add_argument('commit_message', type=str)
-cat_parser.add_argument('property_predicate', type=str, action="append")
-cat_parser.add_argument('property_object', type=str, action="append")
-# cat_parser.add_argument('delete_message', type=str)
+CAT_PARSER = reqparse.RequestParser()
+CAT_PARSER.add_argument('label', type=str)
+CAT_PARSER.add_argument('description', type=str)
+CAT_PARSER.add_argument('commit_message', type=str)
+CAT_PARSER.add_argument('property_predicate', type=str, action="append")
+CAT_PARSER.add_argument('property_object', type=str, action="append")
+CAT_PARSER.add_argument('delete_message', type=str)
 
 
 class CategoryAPI(Resource):
-    # returns category cat_id or all if cat_id is None
+    """
+        The API to create and edit categories, returns rdf graph serializations
+        when successful
+    """
     @classmethod
     @cache.memoize(timeout=3600)
-    def get(self, cat_id=None):
+    def get(cls, cat_id=None):
+        """
+            API to get the category of id cat_id, or if cat_id is None,
+            get the list of category
+        """
         cat_manager_instance = CategoryManager()
         if cat_id is not None:
-            c = cat_manager_instance.load_cat(cat_id)
-            return c.cat_graph.serialize(format='turtle')
+            cat = cat_manager_instance.load_cat(cat_id)
+            return cat.cat_graph.serialize(format='turtle').decode("utf-8")
         else:
             response = []
-            for c in cat_manager_instance.list_cat():
-                response.append(c.cat_graph.serialize(format='turtle'))
+            for cat in cat_manager_instance.list_cat():
+                response.append(cat.cat_graph.serialize(format='turtle')
+                                             .decode("utf-8"))
             return response
 
     # update category cat_id
     @classmethod
-    def put(self, cat_id):
-        args = cat_parser.parse_args()
+    def put(cls, cat_id):
+        """
+            API to edit the category of id cat_id
+        """
+        args = CAT_PARSER.parse_args()
         cat_manager_instance = CategoryManager()
 
         new_property_list = []
 
-        logger.debug(args["property_predicate"])
-        logger.debug(args["property_object"])
-        for property_predicate, property_object in zip(
-                args["property_predicate"],
-                args["property_object"]):
-            if property_object:
-                property_object_to_append = property_object
-                # if URIRef category, we must prefix id with namespace
-                if (app.config["PROPERTY_LIST"]
-                              [property_predicate]
-                              ["object_type"]) == "uriref-category":
-                    property_object_to_append = \
-                        app.config["ONTOLOGY_NAMESPACE"] + property_object
-                    logger.debug(property_object_to_append)
-                new_property_list.append((property_predicate,
-                                          property_object_to_append))
-        logger.debug(new_property_list)
-        c = cat_manager_instance.load_cat(cat_id)
-        c.edit_category(new_description=args["description"],
-                        new_label=args["label"],
-                        new_other_properties=new_property_list)
-        cat_manager_instance.save_cat(c, message=args["commit_message"])
-        logger.debug("put id: "+c.cat_id)
+        LOGGER.debug(args["property_predicate"])
+        LOGGER.debug(args["property_object"])
+        if args["property_predicate"] is not None and \
+           args["property_object"] is not None:
+            for property_predicate, property_object in zip(
+                    args["property_predicate"],
+                    args["property_object"]):
+                if property_object:
+                    property_object_to_append = property_object
+                    # if URIRef category, we must prefix id with namespace
+                    if (app.config["PROPERTY_LIST"]
+                                  [property_predicate]
+                                  ["object_type"]) == "uriref-category":
+                        property_object_to_append = \
+                            app.config["CATEGORY_NAMESPACE"] + property_object
+                        LOGGER.debug(property_object_to_append)
+                    new_property_list.append((property_predicate,
+                                              property_object_to_append))
+        LOGGER.debug(new_property_list)
+        cat = cat_manager_instance.load_cat(cat_id)
+        cat.edit_category(new_description=args["description"],
+                          new_label=args["label"],
+                          new_other_properties=new_property_list)
+        cat_manager_instance.save_cat(cat, message=args["commit_message"])
+        LOGGER.debug("put id: "+cat.cat_id)
         cache.clear()
-        return c.cat_graph.serialize(format='turtle'), 200
+        return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 200
         # Maybe not send the whole cat back, see if it's worth it
 
     @classmethod
-    def post(self):
-        args = cat_parser.parse_args()
+    def post(cls):
+        """
+            API to create a new category
+        """
+        args = CAT_PARSER.parse_args()
         property_list = []
-        logger.debug(args["property_predicate"])
-        logger.debug(args["property_object"])
+        LOGGER.debug(args["property_predicate"])
+        LOGGER.debug(args["property_object"])
         for property_predicate, property_object in zip(
                 request.form.getlist('property_predicate'),
                 request.form.getlist('property_object')):
@@ -85,30 +104,34 @@
                               [property_predicate]
                               ["object_type"]) == "uriref-category":
                     property_list.append((property_predicate,
-                                          app.config["ONTOLOGY_NAMESPACE"]
+                                          app.config["CATEGORY_NAMESPACE"]
                                           + property_object))
                 else:
                     property_list.append((property_predicate,
                                           property_object))
-        logger.debug(property_list)
-        c = Category(label=args["label"],
-                     description=args["description"],
-                     other_properties=property_list)
+        LOGGER.debug(property_list)
+        cat = Category(label=args["label"],
+                       description=args["description"],
+                       other_properties=property_list)
         cat_manager_instance = CategoryManager()
-        cat_manager_instance.save_cat(c, message=args["commit_message"])
-        logger.debug("post id: "+c.cat_id)
+        cat_manager_instance.save_cat(cat, message=args["commit_message"])
+        LOGGER.debug("post id: "+cat.cat_id)
         cache.clear()
-        return c.cat_graph.serialize(format='turtle'), 201
+        return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 201
 
     @classmethod
-    def delete(self, cat_id):
+    def delete(cls, cat_id):
+        """
+            API to delete the category of id cat_id
+        """
+        args = CAT_PARSER.parse_args()
         cat_manager_instance = CategoryManager()
-        if not request.form["delete_message"]:
+        if args["delete_message"] is None:
             message = "Deleting category "+cat_id
         else:
-            message = request.form["delete_message"]
+            message = args["delete_message"]
         cat_manager_instance.delete_cat(cat_id, message=message)
-        logger.debug("delete id: "+cat_id)
+        LOGGER.debug("delete id: "+cat_id)
         cache.clear()
         return 204
 
--- a/src/catedit/config.py.tmpl	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/config.py.tmpl	Wed Dec 17 17:21:06 2014 +0100
@@ -1,17 +1,83 @@
-# Overwriting settings
-class appConfig(object):
+"""
+config.py:
+Contains all settings that can change depending on the deployment environment
+"""
+
+from rdflib import URIRef, RDF, RDFS, Literal
+from rdflib.namespace import SKOS
+
+class AppConfig(object):
+
+    # Debug and running settings
 
-    DEBUG = False
+    HOST = "0.0.0.0"
+    DEBUG = True
+    LOGGING_LEVEL = "DEBUG"
+
+    # WTForms settings
+
+    SECRET_KEY = 'totally-secret-key'
+
+    # Saving file for local persistence
+    FILE_SAVE_DIRECTORY = "../../run/files/"
+
+    # Logging config
+    LOG_FILE_PATH = "../../run/log/log.txt"
     LOGGING = False
-    SERVER_NAME = "0.0.0.0"
 
-    # Github repository settings
+    # Github repository config
 
-    REPOSITORY_NAME = "habitabilite-prototype"
+    REPOSITORY_NAME = "catedit-dev-testing"
     REPOSITORY_OWNER = "catedit-system"
     CATEGORIES_PATH = "categories/"
 
     # Github parameters
 
-    GITHUB_CLIENT_ID = "3e31f3b000a4914f75ef"
-    GITHUB_CLIENT_SECRET = "bc17eb0ec11385628c2e75aacb5ff8ef5f29e490"
+    GITHUB_CLIENT_ID = "github-id-placeholder"
+    GITHUB_CLIENT_SECRET = "github-secret-placeholder"
+
+    # Property List
+
+    PROPERTY_LIST = {
+        "subClassOf": {
+            "descriptive_label_fr": "Sous-classe de",
+            "descriptive_label_en": "Subclass of",
+            "object_type": "uriref-category",
+            "rdflib_class": RDFS.subClassOf,
+            "object_rdflib_class": URIRef,
+        },
+        "value": {
+            "descriptive_label_fr": "Valeur",
+            "descriptive_label_en": "Value",
+            "object_type": "literal",
+            "rdflib_class": RDF.value,
+            "object_rdflib_class": Literal,
+        },
+        "type": {
+            "descriptive_label_fr": "Type",
+            "descriptive_label_en": "Type",
+            "object_type": "uriref-category",
+            "rdflib_class": RDF.type,
+            "object_rdflib_class": URIRef,
+        },
+        "resource": {
+            "descriptive_label_fr": "Ressource",
+            "descriptive_label_en": "Resource",
+            "object_type": "uriref-link",
+            "rdflib_class": RDFS.Resource,
+            "object_rdflib_class": URIRef,
+        },
+        "related": {
+            "descriptive_label_fr": "En relation avec",
+            "descriptive_label_en": "Related to",
+            "object_type": "uriref-category",
+            "rdflib_class": SKOS.related,
+            "object_rdflib_class": URIRef,
+        }
+    }
+
+    # Category persistence parameters
+    # "PersistenceToFile" : will save categories to files on system
+    # "PersistenceToGithub" : will save categories to files on Github
+
+    PERSISTENCE_METHOD = "PersistenceToGithub"
--- a/src/catedit/main.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/main.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,8 +1,12 @@
-from catedit import app
+"""
+main.py:
+script that is used to boot up the application
+"""
 
-from api import api
-from models import *
-from views import *
+from catedit import app
+from catedit.api import api
+from catedit.views import cat_editor, cat_recap, github_login, \
+                          github_callback, logout
 
 if __name__ == '__main__':
     app.run(host=app.config["HOST"])
--- a/src/catedit/models.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/models.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,34 +1,44 @@
-from flask import Flask, request
-from flask.ext.restful import fields
-from rdflib import Graph, RDF, RDFS, BNode, Literal, URIRef
-from uuid import uuid4
+"""
+models.py:
+contains the "principal" objects that will be manipulated by the application:
+* categories
+* helper classes to manage category life cycle
+"""
+
+from rdflib import Graph, RDF, RDFS, Literal, URIRef
+# from uuid import uuid4
 from io import StringIO
 from slugify import slugify
-import persistence
 from catedit import app
-
-logger = app.logger
+import catedit.persistence
 
-"""
-Namespace: ld.iri-research.org/ontology/categorisation/#
-Category URI: ld.iri-research.org/ontology/categorisation/#cat_id
 
-Category Class:
-label is the rdf label of the category
-description is the description of the category
-other_properties is a dictionnary containing every other supported property
-as defined in the PROPERTY_LIST dict in settings.py
-"""
+LOGGER = app.logger
 
 
 class Category(object):
+    """
+        Category Class:
+
+        Init:
+        * label is the rdf label of the category, unique and non-empty
+        * description is the description of the category, unique and non-empty
+        * other_properties is a dictionnary containing every other supported
+        property as defined in the PROPERTY_LIST dict in settings.py
+        * graph is used if we want to create the Category from a turtle
+        rdf graph
+
+        Additional info:
+        * cat_id is a generated, hidden id that uniquely identify
+        a given category
+    """
     def __init__(self, label=None, description=None,
                  other_properties=None, graph=None):
-        if not(graph):
+        if not graph:
             # cat_id = uuid4().hex - Alternate method of generating ids
-            cat_id = "category_id_"+slugify(bytes(label))
+            cat_id = "category_id_"+slugify(label)
             self.cat_graph = Graph()
-            self.this_category = URIRef(app.config["ONTOLOGY_NAMESPACE"] +
+            self.this_category = URIRef(app.config["CATEGORY_NAMESPACE"] +
                                         cat_id)
             self.cat_graph.add((self.this_category, RDF.ID, Literal(cat_id)))
 
@@ -53,12 +63,15 @@
 
         else:
             self.cat_graph = graph
-            self.this_category = self.cat_graph \
-                                     .subjects(predicate=RDF.ID) \
-                                     .next()  # Warning: not foolproof
+            # Warning: not foolproof
+            self.this_category = next(self.cat_graph
+                                          .subjects(predicate=RDF.ID))
 
     @property
     def label(self):
+        """
+            Returns category label
+        """
         return_value = self.cat_graph.value(self.this_category, RDFS.label)
         if return_value is None:
             return None
@@ -67,6 +80,9 @@
 
     @property
     def description(self):
+        """
+            Returns category description
+        """
         return_value = \
             self.cat_graph.value(self.this_category, RDF.Description)
         if return_value is None:
@@ -76,10 +92,16 @@
 
     @property
     def cat_id(self):
+        """
+            Returns category id
+        """
         return self.cat_graph.value(self.this_category, RDF.ID).toPython()
 
     @property
     def properties(self):
+        """
+            Returns category property list
+        """
         property_list = []
         for key in app.config["PROPERTY_LIST"]:
             for obj in self.cat_graph \
@@ -94,10 +116,19 @@
                       new_description=False,
                       new_other_properties=False):
         """
-        Checks if there is a new label and if so apply changes
+            Edits category
+            * new_label is the new label of the category
+            * new_description is the new description of the category
+            * new_other_property is the new property list of the category
+
+            Every argument is optional and defaults to False for consistency,
+            as giving a property list that is None means we want
+            to delete all properties
         """
-        if new_label is not (False or None) and \
-           new_label != self.label:
+
+        if (new_label is not False) and \
+           (new_label is not None) and \
+           (new_label != self.label):
             self.cat_graph.remove((self.this_category,
                                    RDFS.label,
                                    self.cat_graph.label(self.this_category)))
@@ -105,11 +136,9 @@
                                 RDFS.label,
                                 Literal(new_label)))
 
-        """
-        Checks if there is a new description and if so apply changes
-        """
-        if new_description is not (False or None) and \
-           new_description != self.description:
+        if (new_description is not False) and \
+           (new_description is not None) and \
+           (new_description != self.description):
             self.cat_graph.remove((self.this_category,
                                   RDF.Description,
                                   self.cat_graph.value(self.this_category,
@@ -118,27 +147,23 @@
                                RDF.Description,
                                Literal(new_description)))
 
-        """
-        Checks if there is a new property list and if so apply changes
-        (can be [] so it deletes everything)
-        """
         if new_other_properties is not False or \
             (new_other_properties is None and
              self.properties is not None):
-            logger.debug("before suppressing properties: ")
-            logger.debug(self.properties)
-            logger.debug("will replace with: ")
-            logger.debug(new_other_properties)
+            LOGGER.debug("before suppressing properties: ")
+            LOGGER.debug(self.properties)
+            LOGGER.debug("will replace with: ")
+            LOGGER.debug(new_other_properties)
             for key in app.config["PROPERTY_LIST"]:
                 self.cat_graph.remove((self.this_category,
                                        app.config["PROPERTY_LIST"]
                                                  [key]
                                                  ["rdflib_class"],
                                        None))
-            logger.debug("now properties are: ")
-            logger.debug(self.properties)
-            logger.debug("making new properties: ")
-            logger.debug(new_other_properties)
+            LOGGER.debug("now properties are: ")
+            LOGGER.debug(self.properties)
+            LOGGER.debug("making new properties: ")
+            LOGGER.debug(new_other_properties)
             for (predicate, obj) in new_other_properties:
                 self.cat_graph.add((self.this_category,
                                     app.config["PROPERTY_LIST"]
@@ -149,40 +174,50 @@
                                               ["object_rdflib_class"](obj)))
 
 
-"""
-CategoryManager class
-
-This class deals with creation and loading of Category objects
-
-Persistence method is set in config files
-"""
-
-
 class CategoryManager(object):
+    """
+        CategoryManager class
+        This class deals with creation and loading of Category objects
+        Persistence method is set in config.py and used to save
+        and load categories
+    """
     def load_cat(self, cat_id):
-        p = getattr(persistence, app.config["PERSISTENCE_METHOD"])()
-        cat_serial = p.load(name=cat_id)
+        """
+            Loads a category from its id
+        """
+        persistence = getattr(catedit.persistence,
+                              app.config["PERSISTENCE_METHOD"])()
+        cat_serial = persistence.load(name=cat_id)
         loaded_cat_graph = Graph()
         loaded_cat_graph.parse(source=StringIO(cat_serial), format='turtle')
         cat = Category(graph=loaded_cat_graph)
         return cat
 
     def save_cat(self, cat, message=None):
-        p = getattr(persistence, app.config["PERSISTENCE_METHOD"])()
-        p.save(content=cat.cat_graph.serialize(format='turtle'),
-               name=cat.cat_id, message=message)
+        """
+            Saves a category, message serves as commit message
+            if github is used as a persistence method
+        """
+        persistence = getattr(catedit.persistence,
+                              app.config["PERSISTENCE_METHOD"])()
+        persistence.save(content=cat.cat_graph.serialize(format='turtle'),
+                         name=cat.cat_id, message=message)
 
     def delete_cat(self, deleted_cat_id, message=None):
+        """
+            Deletes a category from its id, message serves
+            as commit message if github is used as a persistence method
+        """
         cat_list = self.list_cat()
         for cat in cat_list:
-            if cat.cat_id != app.config["ONTOLOGY_NAMESPACE"]+deleted_cat_id:
+            if cat.cat_id != app.config["CATEGORY_NAMESPACE"]+deleted_cat_id:
                 new_property_list_for_cat = []
                 for (predicate, obj) in cat.properties:
                     if not (
                         (app.config["PROPERTY_LIST"]
                                    [predicate]
                                    ["object_type"] == "uriref-category") and
-                        (obj == (app.config["ONTOLOGY_NAMESPACE"] +
+                        (obj == (app.config["CATEGORY_NAMESPACE"] +
                                  deleted_cat_id))
                     ):
                         new_property_list_for_cat.append((predicate, obj))
@@ -193,13 +228,18 @@
                     cat,
                     message=message+", cleaning up other properties"
                 )
-        p = getattr(persistence, app.config["PERSISTENCE_METHOD"])()
-        p.delete(name=deleted_cat_id, message=message)
+        persistence = getattr(catedit.persistence,
+                              app.config["PERSISTENCE_METHOD"])()
+        persistence.delete(name=deleted_cat_id, message=message)
 
     def list_cat(self):
-        p = getattr(persistence, app.config["PERSISTENCE_METHOD"])()
-        cat_serial_list = p.list()
-        logger.debug(cat_serial_list)
+        """
+            Lists all categories available
+        """
+        persistence = getattr(catedit.persistence,
+                              app.config["PERSISTENCE_METHOD"])()
+        cat_serial_list = persistence.list()
+        # LOGGER.debug(cat_serial_list)
         cat_list = []
         for cat_serial in cat_serial_list:
             loaded_cat_graph = Graph()
@@ -207,6 +247,6 @@
                 source=StringIO(cat_serial),
                 format='turtle'
             )
-            c = Category(graph=loaded_cat_graph)
-            cat_list.append(c)
+            cat = Category(graph=loaded_cat_graph)
+            cat_list.append(cat)
         return cat_list
--- a/src/catedit/persistence.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/persistence.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,76 +1,132 @@
+"""
+persistence.py:
+contains what we need to make a (category) objects (see models.py) persist
+beyond application scope and load them from outside sources
+
+For now we can save, load and list
+* to/from a file (using a turtle rdf graph serialization for categories)
+* to/from a github repository (using a turtle rdf graph serialization
+for categories)
+"""
 from abc import ABCMeta, abstractmethod
-from catedit import app
+from catedit import app, github
 from base64 import b64encode, b64decode
+from flask.ext.github import GitHubError
 import os
 import json
 
+LOGGER = app.logger
+
 
 class Persistence:
+    """
+        Meta-class for all persistence classes
+    """
     __metaclass__ = ABCMeta
 
     @abstractmethod
     def save(self, **kwargs):
+        """
+            Abstract - Saves object
+        """
         return
 
     @abstractmethod
     def delete(self, **kwargs):
+        """
+            Abstract - Deletes object
+        """
         return
 
     @abstractmethod
     def load(self, **kwargs):
+        """
+            Abstract - Loads object
+        """
         return
 
     @abstractmethod
     def list(self, **kwargs):
+        """
+            Abstract - Lists objects
+        """
         return
 
-"""
-kwargs for saving to a file should be
-
-pathDir = directory to save the file in
-fileName = name of the file to write in
-content = desired content of the file
-
-There is still adjustments to do on this
-"""
-
 
 class PersistenceToFile(Persistence):
+    """
+        Persistence Class for saving to a local file (see config.py
+        for tweaking)
+
+        Expected kwargs for saving to/loading from a file are:
+        * name : name of the file to write in/read from
+        * content : desired content of the file when writing
+    """
     def save(self, **kwargs):
+        """
+            Saves to a file
+        """
         path_to_save = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
-        file = open(path_to_save, 'wb')
-        file.write(kwargs["content"])
-        file.close()
+        file_to_save = open(path_to_save, 'wb')
+        file_to_save.write(kwargs["content"])
+        file_to_save.close()
 
     def load(self, **kwargs):
+        """
+            Loads from a file
+        """
         path_to_load = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
-        file = open(path_to_load, 'rb')
-        file_content = file.read()
-        file.close()
+        file_to_load = open(path_to_load, 'rb')
+        file_content = file_to_load.read()
+        file_to_load.close()
         return file_content
 
     def delete(self, **kwargs):
+        """
+            Deletes a file
+        """
         path_to_delete = app.config["FILE_SAVE_DIRECTORY"]+kwargs["name"]
         os.remove(path_to_delete)
 
     # IDEA: return { file_name: file_content } type dict
     def list(self, **kwargs):
+        """
+            Lists all files in file directory (as set in config.py)
+        """
         file_content_list = []
         for file_name in os.listdir(app.config["FILE_SAVE_DIRECTORY"]):
             if not file_name or file_name[0] == ".":
                 continue
-            file = open(app.config["FILE_SAVE_DIRECTORY"]+file_name)
-            file_content = file.read()
-            file.close()
+            path_to_load = open(app.config["FILE_SAVE_DIRECTORY"]+file_name)
+            file_content = path_to_load.read()
+            path_to_load.close()
             file_content_list.append(file_content)
-        # logger.debug(file_content_list)
+        # LOGGER.debug(file_content_list)
         return file_content_list
 
 
 class PersistenceToGithub(Persistence):
+    """
+        Persistence Class for saving to/loading from a Github repository (see
+        config.py for tweaks)
+
+        Expected kwargs for saving to/loading from a Github repository are:
+        * name : name of the file to write in/read from
+        * content : desired content of the file when writing
+        * message : when saving or deleting, commit message to be saved
+        to Github
+    """
     def save(self, **kwargs):
-        # logger.debug(kwargs["content"])
-        request_data = {"content": b64encode(kwargs["content"]),
+        """
+            Saves to a Github repository
+
+            IMPORTANT: To save to a file Github, we use a PUT request, and
+            if the file already exists we must put its sha in the request data.
+            The first try-except block actually expects an error if creating a
+            new file because we need to check if the file already exists
+        """
+        # LOGGER.debug(kwargs["content"])
+        request_data = {"content": str(b64encode(kwargs["content"]), "ascii"),
                         "message": kwargs["message"]}
         try:
             filedict = github.get("repos/"
@@ -81,8 +137,14 @@
                                   + kwargs["name"])
             request_data["sha"] = filedict["sha"]
         except GitHubError:
-            pass
-        # logger.debug(json.dumps(request_data))
+            LOGGER.debug("Github sent an error, either: \
+                         1- You're trying to create a new file named : "
+                         + kwargs["name"] + " OR \
+                         2- You're trying to edit a file named : "
+                         + kwargs["name"] + ", \
+                         in which case something went wrong \
+                         trying to get its sha")
+        # LOGGER.debug(json.dumps(request_data))
         try:
             github.request('PUT',
                            "repos/"
@@ -93,9 +155,15 @@
                            + kwargs["name"],
                            data=json.dumps(request_data))
         except GitHubError:
-            pass
+            LOGGER.debug("Github Error trying to update file: "+kwargs["name"])
+            LOGGER.debug("Github sent an error, if 404, either you may not \
+                         have access to the repository or it doesn't exist ")
+            LOGGER.debug(GitHubError.response.text)
 
     def load(self, **kwargs):
+        """
+            Loads from a Github repository
+        """
         try:
             filedict = github.get("repos/"
                                   + app.config["REPOSITORY_OWNER"]+"/"
@@ -103,12 +171,18 @@
                                   + "/contents/"
                                   + app.config["CATEGORIES_PATH"]
                                   + kwargs["name"])
-            file_content = b64decode(filedict["content"])
+            file_content = str(b64decode(filedict["content"]), "utf-8")
         except GitHubError:
-            pass
+            LOGGER.debug("Github Error trying to get file: "+kwargs["name"])
+            LOGGER.debug("Github sent an error, if 404, either you may not \
+                         have access to the repository or it doesn't exist ")
+            LOGGER.debug(GitHubError.response.text)
         return file_content
 
     def delete(self, **kwargs):
+        """
+            Deletes from a Github repository
+        """
         request_data = {"message": kwargs["message"]}
         try:
             filedict = github.get("repos/"
@@ -119,7 +193,9 @@
                                   + kwargs["name"])
             request_data["sha"] = filedict["sha"]
         except GitHubError:
-            pass
+            LOGGER.debug("Github Error trying to get sha for \
+                         file: "+kwargs["name"])
+            LOGGER.debug(GitHubError.response.text)
 
         try:
             github.request('DELETE',
@@ -129,9 +205,13 @@
                            + kwargs["name"],
                            data=json.dumps(request_data))
         except GitHubError:
-            pass
+            LOGGER.debug("Github Error trying to delete file: "+kwargs["name"])
+            LOGGER.debug(GitHubError.response.text)
 
     def list(self, **kwargs):
+        """
+            Lists all files in the github repository (as set in config.py)
+        """
         filenames_list = []
         try:
             files_in_repo = github.get("repos/"
@@ -139,10 +219,17 @@
                                        + app.config["REPOSITORY_NAME"]
                                        + "/contents/"
                                        + app.config["CATEGORIES_PATH"])
-            filenames_list = [file["name"] for file in files_in_repo]
-            # logger.debug(filenames_list)
+            filenames_list = [github_file["name"]
+                              for github_file in files_in_repo]
+            # LOGGER.debug(filenames_list)
         except GitHubError:
-            pass
+            LOGGER.debug("Github Error trying to get the file list in the \
+                         category repository")
+            LOGGER.debug("NOTE: Github sent an error, if 404 either you \
+                         may not have access to the repository or it doesn't \
+                         exist")
+            LOGGER.debug(GitHubError.response.text)
+
         file_content_list = []
         for filename in filenames_list:
             try:
@@ -152,8 +239,10 @@
                                       + "/contents/"
                                       + app.config["CATEGORIES_PATH"]
                                       + filename)
-                file_content_list.append(b64decode(filedict["content"]))
+                file_content_list.append(str(b64decode(filedict["content"]),
+                                         "utf-8"))
             except GitHubError:
-                pass
-        # logger.debug(file_content_list)
+                LOGGER.debug("Github Error trying to get file: "+filename)
+                LOGGER.debug(GitHubError.response.text)
+        LOGGER.debug(file_content_list)
         return file_content_list
--- a/src/catedit/settings.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/settings.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,83 +1,15 @@
-from rdflib import URIRef, RDF, RDFS, Literal
-from rdflib.namespace import SKOS
-
-
-class appSettings(object):
+"""
+settings.py:
+Contains all settings that are needed but are not to be changed
+"""
 
-    # Debug and running settings
-
-    HOST = "0.0.0.0"
-    DEBUG = True
-    LOGGING_LEVEL = "DEBUG"
+class AppSettings(object):
 
     # WTForms settings
 
     WTF_CSRF_ENABLED = True
-    SECRET_KEY = 'totally-secret-key'
 
     # RDF Namespace and prefixes
 
     ONTOLOGY_NAMESPACE = "http://ld.iri-research.org/ontology/categorisation#"
-    CATEGORY_NAMESPACE = "http://ld.iri-research.org/categorisation/category/"
-
-    # Saving file for local persistence
-    FILE_SAVE_DIRECTORY = "../../run/files/"
-
-    # Logging config
-    LOG_FILE_PATH = "../../run/log/log.txt"
-    LOGGING = False
-
-    # Github repository config
-
-    REPOSITORY_NAME = "catedit-dev-testing"
-    REPOSITORY_OWNER = "catedit-system"
-    CATEGORIES_PATH = "categories/"
-
-    # Github parameters
-
-    GITHUB_CLIENT_ID = "3e31f3b000a4914f75ef"
-    GITHUB_CLIENT_SECRET = "bc17eb0ec11385628c2e75aacb5ff8ef5f29e490"
-
-    # Property List
-
-    PROPERTY_LIST = {
-        "subClassOf": {
-            "descriptive_label_fr": "Sous-classe de",
-            "descriptive_label_en": "Subclass of",
-            "object_type": "uriref-category",
-            "rdflib_class": RDFS.subClassOf,
-            "object_rdflib_class": URIRef,
-        },
-        "value": {
-            "descriptive_label_fr": "Valeur",
-            "descriptive_label_en": "Value",
-            "object_type": "literal",
-            "rdflib_class": RDF.value,
-            "object_rdflib_class": Literal,
-        },
-        "type": {
-            "descriptive_label_fr": "Type",
-            "descriptive_label_en": "Type",
-            "object_type": "uriref-category",
-            "rdflib_class": RDF.type,
-            "object_rdflib_class": URIRef,
-        },
-        "resource": {
-            "descriptive_label_fr": "Ressource",
-            "descriptive_label_en": "Resource",
-            "object_type": "uriref-link",
-            "rdflib_class": RDFS.Resource,
-            "object_rdflib_class": URIRef,
-        },
-        "related": {
-            "descriptive_label_fr": "En relation avec",
-            "descriptive_label_en": "Related to",
-            "object_type": "uriref-category",
-            "rdflib_class": SKOS.related,
-            "object_rdflib_class": URIRef,
-        }
-    }
-
-    # Category persistence parameters
-
-    PERSISTENCE_METHOD = "PersistenceToFile"
+    CATEGORY_NAMESPACE = "http://ld.iri-research.org/ontology/categorisation/category#"
--- a/src/catedit/templates/cateditor.html	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/templates/cateditor.html	Wed Dec 17 17:21:06 2014 +0100
@@ -31,7 +31,7 @@
             <li class="active"><a>Editeur de catégorie: {% if cat_id: %} Edition {% else %} Création {% endif %}</a></li>
           </ul>
           <div class="navbar-text navbar-right">
-            {% if not session.get("user_code", None)%} Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
+            {% 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>
         </div>
@@ -106,7 +106,7 @@
                     <tr id="property_tr{{property_count}}">
                       {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
                         {% for cat in cat_list %}
-                          {% if object == config["ONTOLOGY_NAMESPACE"]+cat.cat_id %}
+                          {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
                             <input type="hidden" id="property_predicate{{ property_count }}" name="property_predicate" value="{{predicate}}"/>
                             <input type="hidden" id="property_object{{ property_count }}" name="property_object" value="{{cat.cat_id}}"/>
                             <td id="predicate_td{{ property_count }}">
--- a/src/catedit/templates/catrecap.html	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/templates/catrecap.html	Wed Dec 17 17:21:06 2014 +0100
@@ -48,7 +48,7 @@
               <li><a href="{{ url_for('cat_editor') }}">Editeur de catégorie: Création</a></li>
             </ul>
             <div class="navbar-text navbar-right">
-              {% if not session.get("user_code", None)%}Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
+              {% 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>
           </div>
@@ -97,7 +97,7 @@
                         <dd>
                           {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
                             {% for cat in cat_list %}
-                              {% if object == config["ONTOLOGY_NAMESPACE"]+cat.cat_id %}
+                              {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
                                 {{ cat.cat_label }}
                               {% endif %}
                             {% endfor %}
--- a/src/catedit/views.py	Tue Dec 16 11:14:55 2014 +0100
+++ b/src/catedit/views.py	Wed Dec 17 17:21:06 2014 +0100
@@ -1,17 +1,27 @@
+"""
+views.py:
+The views functions that handle the front-end of the application
+"""
+
 from catedit import app, github
-from models import Category, CategoryManager
+from catedit.models import Category
 from flask import render_template, request, redirect, url_for, session
 from flask.ext.github import GitHubError
 from flask_wtf import Form
-from api import CategoryAPI
+from catedit.api import CategoryAPI
 from wtforms import StringField, TextAreaField
 from wtforms.validators import DataRequired
 from rdflib import Graph
 from io import StringIO
 
-logger = app.logger
+LOGGER = app.logger
+
 
 class NewCategoryMinimalForm(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()]
@@ -26,31 +36,37 @@
         )
 
 
-@app.route('/', methods=['GET'])
-@app.route('/catrecap', methods=['GET'])
-@app.route('/catrecap/delete/<string:delete_cat_id>', methods=['POST'])
-def cat_recap(delete_cat_id=None):
+@app.route('/catrecap/delete-<delete_cat_id>', methods=['POST'])
+@app.route('/', defaults={'delete_cat_id': None}, methods=['GET'])
+@app.route('/catrecap', defaults={'delete_cat_id': None}, methods=['GET'])
+def cat_recap(delete_cat_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.
+    """
     cat_api_instance = CategoryAPI()
-    # list categories
     if delete_cat_id is None:
+        LOGGER.debug("Category to delete is None")
         serialized_cat_list = cat_api_instance.get()
-        logger.debug(serialized_cat_list)
+        # LOGGER.debug(serialized_cat_list)
         cat_list = []
-        logger.debug(cat_list)
         for serialized_cat in serialized_cat_list:
             cat_rdf_graph = Graph()
             cat_rdf_graph.parse(source=StringIO(serialized_cat),
                                 format='turtle')
-            c = Category(graph=cat_rdf_graph)
+            cat = Category(graph=cat_rdf_graph)
 
-            cat_list.append({"cat_label": c.label,
-                             "cat_description": c.description,
-                             "cat_id": c.cat_id,
-                             "cat_properties": c.properties})
-            logger.debug(c.properties)
+            cat_list.append({"cat_label": cat.label,
+                             "cat_description": cat.description,
+                             "cat_id": cat.cat_id,
+                             "cat_properties": cat.properties})
+            # LOGGER.debug(c.properties)
         return render_template('catrecap.html',
                                cat_list=cat_list)
     else:
+        LOGGER.debug("Category "+delete_cat_id+" will be deleted.")
         cat_api_instance.delete(delete_cat_id)
         return redirect(url_for('cat_recap'))
 
@@ -58,6 +74,10 @@
 @app.route('/cateditor', methods=['GET', 'POST'])
 @app.route('/cateditor/<string:cat_id>', methods=['GET', 'POST'])
 def cat_editor(cat_id=None):
+    """
+        View that handles creation and edition of categories. Template is
+        cateditor.html, located in src/templates
+    """
     cat_api_instance = CategoryAPI()
 
     serialized_cat_list = cat_api_instance.get()
@@ -66,40 +86,42 @@
         cat_rdf_graph = Graph()
         cat_rdf_graph.parse(source=StringIO(serialized_cat),
                             format='turtle')
-        c = Category(graph=cat_rdf_graph)
+        cat = Category(graph=cat_rdf_graph)
 
-        cat_list.append({"cat_label": c.label,
-                         "cat_description": c.description,
-                         "cat_id": c.cat_id,
-                         "cat_properties": c.properties})
+        cat_list.append({"cat_label": cat.label,
+                         "cat_description": cat.description,
+                         "cat_id": cat.cat_id,
+                         "cat_properties": cat.properties})
 
     if cat_id is not None:
-        catSerial = cat_api_instance.get(cat_id)
+        specific_serialized_cat = cat_api_instance.get(cat_id)
 
         cat_rdf_graph = Graph()
-        cat_rdf_graph.parse(source=StringIO(catSerial),
+        cat_rdf_graph.parse(source=StringIO(specific_serialized_cat),
                             format='turtle')
-        c = Category(graph=cat_rdf_graph)
+        cat = Category(graph=cat_rdf_graph)
 
         setattr(NewCategoryMinimalForm,
                 'label',
                 StringField("Nom de la categorie",
                             validators=[DataRequired()],
-                            default=c.label))
+                            default=cat.label))
         setattr(NewCategoryMinimalForm,
                 'description',
                 TextAreaField("Description de la categorie",
                               validators=[DataRequired()],
-                              default=c.description))
-        logger.debug("CatForm fields preset to "+c.label+" and "+c.description)
+                              default=cat.description))
+        LOGGER.debug("CatForm fields preset to "
+                     + cat.label + " and "
+                     + cat.description)
 
         cat_form = NewCategoryMinimalForm(request.form)
 
         # GET + cat_id = Edit cat form
         if request.method == 'GET':
             return render_template('cateditor.html',
-                                   cat_id=c.cat_id,
-                                   cat_properties=c.properties,
+                                   cat_id=cat.cat_id,
+                                   cat_properties=cat.properties,
                                    form=cat_form,
                                    cat_list=cat_list)
 
@@ -110,7 +132,7 @@
         else:
             return render_template('cateditor.html',
                                    cat_id=cat_id,
-                                   cat_properties=c.properties,
+                                   cat_properties=cat.properties,
                                    form=cat_form,
                                    cat_list=cat_list)
 
@@ -144,43 +166,69 @@
 
 @app.route('/catedit-github-login')
 def github_login():
-    return github.authorize(scope="repo")
+    """
+        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 app.config["PERSISTENCE_METHOD"] == "PersistenceToGithub":
+        return github.authorize(scope="repo")
+    elif app.config["PERSISTENCE_METHOD"] == "PersistenceToFile":
+        session["user_logged"] = True
+        session["user_can_edit"] = True
+        session["user_login"] = "FileEditUser"
+        return redirect(url_for('cat_recap'))
 
 
 @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
     session["user_code"] = oauth_code
     session["user_logged"] = True
     session["user_login"] = github.get("user")["login"]
     try:
-        repoList = []
-        repoList = github.get("user/repos")
-        for repo in repoList:
-            logger.debug(repo["name"])
+        repo_list = []
+        repo_list = github.get("user/repos")
+        for repo in repo_list:
+            LOGGER.debug(repo["name"])
         session["user_can_edit"] = True
         if not any(repo["name"] == app.config["REPOSITORY_NAME"]
-                   for repo in repoList):
+                   for repo in repo_list):
             session["user_can_edit"] = False
-        logger.debug(session["user_can_edit"])
+        LOGGER.debug(session["user_can_edit"])
     except GitHubError:
-        logger.debug("error getting repos!")
-        pass
+        LOGGER.debug("error getting repos!")
 
-    logger.debug(session["user_login"])
+    LOGGER.debug(session["user_login"])
     return redirect(url_for('cat_recap'))
 
 
 @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")
+        # 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_code"] = None
     session["user_logged"] = None
     session["user_login"] = None
--- a/virtualenv/requirements.txt	Tue Dec 16 11:14:55 2014 +0100
+++ b/virtualenv/requirements.txt	Wed Dec 17 17:21:06 2014 +0100
@@ -1,8 +1,20 @@
-rdflib
-flask
-flask-restful
-flask-wtf
-Github-Flask
-flask-cache
-python-slugify
-pep8
+Flask==0.10.1
+Flask-Cache==0.13.1
+Flask-RESTful==0.3.1
+Flask-WTF==0.10.3
+GitHub-Flask==2.0.0
+Jinja2==2.7.3
+MarkupSafe==0.23
+Unidecode==0.04.16
+WTForms==2.0.1
+Werkzeug==0.9.6
+aniso8601==0.90
+isodate==0.5.1
+itsdangerous==0.24
+pep8==1.5.7
+pyparsing==2.0.3
+python-slugify==0.1.0
+pytz==2014.10
+rdflib==4.1.2
+requests==2.5.0
+six==1.8.0