API versioning via namespace
authordurandn
Mon, 22 Feb 2016 10:41:11 +0100
changeset 590 3be2a86981c2
parent 589 0ae11aa255a3
child 591 7a548f69251c
API versioning via namespace
server/python/django2/renkanmanager/docs/index.md
server/python/django2/renkanmanager/migrations/0004_auto_20160212_1106.py
server/python/django2/renkanmanager/tests/__init__.py
server/python/django2/renkanmanager/tests/tests_renkan.py
server/python/django2/renkanmanager/tests/tests_revision.py
server/python/django2/renkanmanager/tests/tests_workspace.py
server/python/django2/renkanmanager/tests/v1_0/__init__.py
server/python/django2/renkanmanager/tests/v1_0/tests_renkan.py
server/python/django2/renkanmanager/tests/v1_0/tests_revision.py
server/python/django2/renkanmanager/tests/v1_0/tests_workspace.py
server/python/django2/renkanmanager/urls.py
server/python/django2/renkanmanager/versioning/__init__.py
server/python/django2/renkanmanager/versioning/v1_0_urls.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/docs/index.md	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,212 @@
+# Renkan API Documentation
+
+Ce document présente le fonctionnement et les différentes méthodes de l'API Renkan, basée sur DjangoRestFramework
+
+### Objets manipulés et représentation en JSON
+
+##### Renkan
+
+L'objet Renkan est le principal point d'entrée de l'API.    
+Chaque Renkan est associé à au plus un workspace et au moins une révision
+
+* Représentation JSON   
+
+```sh
+    {
+        "id" : id du renkan,
+        "title" : titre du renkan,
+        "content" : contenu (JSON) du renkan,
+        "current_revision_id" : id de la révision courante du 	renkan,
+        "source_revision_id" : id de la révision ayant servi à copier le renkan, chaîne vide sinon,
+        "workspace_id" : si le renkan est assigné à un workspace, id du workspace du renkan,
+        "revision_count": le nombre de révisions du renkan au moment du la requête,
+        "created_by" : username du créateur du renkan,
+        "last_updated_by" : username du dernier utilisateur à avoir modifié le renkan,
+        "creation_date" : date de création du renkan,
+        "modification_date" : date de dernière modification du renkan,
+    }
+```
+
+##### Révisions
+
+Une révision représente l'état d'un renkan à un instant donné.    
+Chaque révision est associé à un unique Renkan. Lorsqu'un renkan est modifié, on peut soit altérer la révision courante, soit créer une nouvelle révision. La dernière révision d'un renkan donné créée est la "révision courante" de ce renkan.    
+
+C'est au niveau de l'objet Révision que sont stockées les informations de titre et de contenu d'un Renkan. 
+
+* Représentation JSON   
+
+```sh
+    {
+    	"id" : id de la revision
+    	"title" : titre de la revision
+    	"content" : contenu (JSON) de la revision
+    	"parent_renkan_id" : id du renkan associé à la révision 
+    	"workspace_id" : si le renkan associé à la révision est assigné à un workspace, 
+    	id du workspace du renkan,
+    	"renkan_created_by" : username du créateur du renkan
+    	"renkan_last_updated_by" : username du dernier utilisateur à avoir modifié le renkan
+    	"revision_created_by" : username du créateur de la révision
+    	"revision_last_updated_by" : username du dernier utilisateur à avoir modifié la révision	
+    	"revision_modification_date" :  date de dernière modification de la révision
+    }
+```
+
+##### Workspaces
+Un workspace (ou espace) renkan est une structure (optionnelle) regroupant un ou plusieurs renkans. 
+
+* Représentation JSON   
+
+```sh
+   {
+    	"id" : id du workspace
+    	"title" : titre du workspace
+    	"renkan_count": nombre de renkans associés au workspace
+    	"revision_created_by" : username du créateur du workspace
+    	"creation_date": date de création du workspace
+    }
+```
+
+### Liste exhaustive des endpoints de l'API
+
+#### Endpoints Renkan
+1. **Créer un Renkan**    
+```POST /renkan-api/renkans/```
+
+Données à passer en JSON (facultatif):
+```
+    {
+	    "title": titre du renkan à créer (facultatif)
+	    "content": JSON du renkan à créer (facultatif)
+    }
+```
+
+**Note:** Si aucun titre n'est fournie le renkan sera intitulé "Untitled Renkan". Si aucun contenu n'est fourni un JSON minimal sera généré
+
+Si succès: Renvoie ```201 CREATED``` et le json associé au renkan créé
+
+2. **Dupliquer un Renkan**    
+```POST /renkan-api/renkans/?source_renkan_id=<:renkan_id>```    
+
+Données à passer en JSON:
+```
+{
+	"title": titre de la copie (facultatif)
+}
+```    
+
+**Note:** Dans l'implémentation actuelle, si la donnée "title" n'est pas fournie, le titre du renkan copié sera identique au titre du renkan source.
+
+Si succès: renvoie ```201 CREATED``` et le json décrivant le renkan créé.    
+Si échec: renvoie un ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant
+
+3. **Modifier un Renkan**    
+```PUT /renkan-api/renkans/<:renkan_id>```
+
+Données à passer en JSON:
+```
+{
+	"title": nouveau titre du renkan
+}
+```
+
+Si succès: renvoie ```200 OK``` et le json associé au renkan modifié    
+Si échec:  renvoie un ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant.
+
+4. **Obtenir la liste des Renkan**  
+```GET /renkan-api/renkans/```
+
+Renvoie la liste des renkans auxquels l'utilisateur authentifié a accès en lecture.
+
+Si succès: Renvoie ```200 OK```et une liste où chaque élément correspond à un json de renkan.
+
+5. **Obtenir les informations sur un Renkan**    
+```GET /renkan-api/renkans/<:renkan_id>```    
+
+Si succès: renvoie ```200 OK``` et le json associé au renkan requêté    
+Si échec: renvoie ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant.
+
+6. **Supprimer un renkan**  
+```DELETE /renkan-api/renkans/<:renkan_id>```
+
+Si succès: renvoie ```204 NO CONTENT```     
+Si échec: renvoie un ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant.
+
+#### Endpoints Révision
+1. **Obtenir la liste des révisions pour un Renkan donné**	
+```GET /renkan-api/renkans/<:renkan_id>/revisions/```
+
+Si succès: Renvoie ```200 OK``` et une liste où chaque élément est un json correspondant à une révision.    
+Si échec: Renvoie un ```404 NOT FOUND``` si le ```<:renkan_id>``` ne correspond à aucun renkan existant
+
+2. **Obtenir les informations sur une révision**    
+```GET /renkan-api/renkans/<:renkan_id>/revisions/<:revision_id>```
+
+Si succès: renvoie ```200 OK``` et le json associé à la révision requêtée     
+Si échec: renvoie un ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant ou que ```<:revision_id>``` ne correspond à aucune révision existante.
+
+3. **Supprimer une révision**   
+```DELETE /renkan-api/renkans/<:renkan_id>/revisions/<:revision_id>```
+
+**Note:** il est impossible de supprimer la "révision courante" d'un renkan.
+
+Si succès: renvoie ```204 NO CONTENT```  
+Si échec: renvoie un ```404 NOT FOUND``` si ```<:renkan_id>``` ne correspond à aucun renkan existant ou si ```<:revision_id>``` ne correspond à aucune révision existante
+
+#### Endpoints Workspace
+1. **Créer un Workspace**   
+```POST /renkan-api/workspaces/```
+
+Données à fournir en JSON (facultatif):
+``` 
+{
+    "title": titre du workspace à créer
+}
+```
+
+Si succès: Renvoie ```201 CREATED``` et le json associé au workspace créé
+
+2. **Créer un Renkan dans un Workspace donné**  
+```POST /renkan-api/workspaces/<:workspace_id>/renkans/```
+
+Données (facultatif):
+```
+{
+	"title": titre du renkan à créer (facultatif)
+	"content": JSON du renkan à créer (facultatif)
+}
+```
+
+**Note:** Si aucun titre n'est fournie le renkan sera intitulé "Untitled Renkan". Si aucun contenu n'est fourni un JSON minimal sera généré
+
+Si succès: Renvoie ```201 CREATED``` et le json associé au renkan créé  
+Si échec: Renvoie un ```404 NOT FOUND``` si le ```<:workspace_id>``` ne correspond à aucun workspace exis-tant
+
+3. **Obtenir la liste des Workspaces**  
+```GET /renkan-api/workspaces/ ```
+
+Si succès: renvoie ```200 OK``` et une liste où chaque élément est un json correspondant à un workspace.
+
+4. **Obtenir la liste des Renkan associé à un Workspace donné**     
+```GET /renkan-api/workspace/<:workspace_id>/renkans/```
+
+Renvoie la liste des renkans (associés au workspace) auxquels l'utilisateur authentifié a accès en lecture.
+
+Si succès: Renvoie ```200 OK``` et une liste où chaque élément correspond à un json de renkan.    
+Si échec: Renvoie un ```404 NOT FOUND``` si le ```<:workspace_id>``` ne correspond à aucun workspace exis-tant
+
+5. **Obtenir les informations sur un Workspace**    
+```GET /renkan-api/workspaces/```
+
+Si succès:renvoie ```200 OK``` et une liste où chaque élément est un json correspondant à un workspace.    
+Si échec: renvoie un ```404 NOT FOUND``` si le ```<:workspace_id>``` ne correspond à aucun workspace existant
+
+6. **Supprimer un Workspace**   
+```DELETE /renkan-api/workspaces/<:workspace_id>```
+
+Note: il est impossible de supprimer un workspace s'il reste au moins un renkan assigné à celui-ci.
+
+Si succès: renvoie ```204 NO CONTENT```     
+Si échec: renvoie un ```404 NOT FOUND``` si le ```<:workspace_id>``` ne correspond à aucun workspace existant 
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/migrations/0004_auto_20160212_1106.py	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.1 on 2016-02-12 11:06
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('renkanmanager', '0003_auto_20160105_0954'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='renkan',
+            options={'permissions': (('view_renkan', 'Can view renkan'),)},
+        ),
+        migrations.AlterModelOptions(
+            name='revision',
+            options={'permissions': (('view_revision', 'Can view revision'),)},
+        ),
+        migrations.AlterModelOptions(
+            name='workspace',
+            options={'permissions': (('view_workspace', 'Can view workspace'),)},
+        ),
+    ]
--- a/server/python/django2/renkanmanager/tests/__init__.py	Tue Feb 02 16:21:20 2016 +0100
+++ b/server/python/django2/renkanmanager/tests/__init__.py	Mon Feb 22 10:41:11 2016 +0100
@@ -1,3 +1,3 @@
-from .tests_renkan import *
-from .tests_workspace import *
-from .tests_revision import *
\ No newline at end of file
+from .v1_0.tests_renkan import *
+from .v1_0.tests_workspace import *
+from .v1_0.tests_revision import *
\ No newline at end of file
--- a/server/python/django2/renkanmanager/tests/tests_renkan.py	Tue Feb 02 16:21:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,604 +0,0 @@
-import json
-
-from django.contrib.auth import get_user_model
-from django.core.urlresolvers import reverse
-from renkanmanager.models import Renkan, Revision
-from rest_framework import status
-from rest_framework.test import APITestCase
-
-
-User = get_user_model()
-
-class RenkanTests(APITestCase):
-    
-    def setUp(self):
-        User.objects.create_superuser("blop", "blop@blop.com", "blop")
-        self.user = User.objects.get(username="blop")
-        _ = self.client.login(username="blop", password="blop")
-        User.objects.create_user("blip", "blip@blip.com", "blip")
-        self.unauthorized_user = User.objects.get(username="blip")
-        
-        # POSTing test workspace
-        post_url = reverse("workspace_list")
-        test_title = "bleh_title"
-        data = {"title": test_title}
-        post_response = self.client.post(post_url, data, format="json")
-        self.test_workspace_guid = json.loads(post_response.content.decode()).get("id")
-        self.first_test_title = "test_title_1"
-        self.first_test_content = json.dumps({
-            "id": "",
-            "title": "test_title_1",
-            "description": "test_description_1",
-            "created": "",
-            "updated": "",
-            "edges": [],
-            "nodes": [],
-            "users": [],
-            "space_id": "",
-            "views": []
-        })
-        self.second_test_title = "test_title_1"
-        self.second_test_content = json.dumps({
-            "id": "",
-            "title": "test_title_2",
-            "description": "test_description_2",
-            "created": "",
-            "updated": "",
-            "edges": [],
-            "nodes": [],
-            "users": [],
-            "space_id": "",
-            "views": []
-        })
-        self.third_test_title = "test_title_1"
-        self.third_test_content = json.dumps({
-            "id": "",
-            "title": "test_title_3",
-            "description": "test_description_3",
-            "created": "",
-            "updated": "",
-            "edges": [],
-            "nodes": [],
-            "users": [],
-            "space_id": "",
-            "views": []
-        })
-        
-    def test_create_renkan(self):
-        
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        data = {"title": self.first_test_title, "content": self.first_test_content}
-        
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(Renkan.objects.count(), 1)
-        self.assertEqual(Revision.objects.count(), 1)
-        
-        # Checking POSTed Renkan data
-        post_response_dict = json.loads(post_response.content.decode())
-        new_renkan_guid = post_response_dict.get("id", "")
-        new_renkan = Renkan.objects.get(renkan_guid=new_renkan_guid)
-        # GUIDs and username
-        self.assertEqual(self.user.username, post_response_dict.get("created_by", ""))
-        self.assertEqual(self.user.username, post_response_dict.get("last_updated_by", ""))
-        self.assertEqual(self.user.username, new_renkan.creator.username)
-        # Content and title extracted from revision
-        self.assertEqual(new_renkan.title, self.first_test_title)
-        self.assertEqual(new_renkan.content, self.first_test_content)
-        # Revision count
-        self.assertEqual(new_renkan.revision_count, 1)
-        self.assertEqual(post_response_dict.get("revision_count", ""), 1)
-        
-        # Checking POSTed Renkan revision data
-        new_renkan_revision_guid = post_response_dict.get("current_revision_id", "")
-        new_renkan_revision = Revision.objects.get(revision_guid = new_renkan_revision_guid)
-        # GUIDs and username
-        self.assertEqual(self.user.username, new_renkan_revision.creator.username)
-        self.assertEqual(self.user.username, new_renkan_revision.last_updated_by.username)
-        # Title and content
-        #    in the response
-        self.assertEqual(post_response_dict.get("title", ""), self.first_test_title)
-        self.assertEqual(post_response_dict.get("content", ""), self.first_test_content)
-        #    in the database
-        self.assertEqual(new_renkan_revision.title, self.first_test_title)
-        self.assertEqual(new_renkan_revision.content, self.first_test_content)
-        
-        # Checking permission
-        self.assertTrue(self.user.has_perm("view_renkan", new_renkan))
-        
-        ###################################################
-        # POSTing new Renkan into workspace
-        ###################################################
-        
-        post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
-        data = {"title": self.second_test_title, "content": self.second_test_content}
-        
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(Renkan.objects.count(), 2)
-        self.assertEqual(Revision.objects.count(), 2)
-        
-        # Checking POSTed Renkan data
-        post_response_dict = json.loads(post_response.content.decode())
-        new_renkan_guid = post_response_dict.get("id", "")
-        new_renkan = Renkan.objects.get(renkan_guid=new_renkan_guid)
-        # GUIDs and username
-        self.assertEqual(self.test_workspace_guid, post_response_dict.get("workspace_id", ""))
-        self.assertEqual(self.test_workspace_guid, new_renkan.workspace_guid)
-        self.assertEqual(self.user.username, post_response_dict.get("created_by", ""))
-        self.assertEqual(self.user.username, post_response_dict.get("last_updated_by", ""))
-        self.assertEqual(self.user.username, new_renkan.creator.username)
-        # Content and title extracted from revision
-        self.assertEqual(new_renkan.title, self.second_test_title)
-        self.assertEqual(new_renkan.content, self.second_test_content)
-        # Revision count
-        self.assertEqual(new_renkan.revision_count, 1)
-        self.assertEqual(post_response_dict.get("revision_count", ""), 1)
-        
-        ###################################################
-        # POSTing new Renkan with no content
-        ###################################################
-        post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
-        test_title = "bleh_title_2"
-        data = {"title": test_title}
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(Renkan.objects.count(), 3)
-        self.assertEqual(Revision.objects.count(), 3)
-        
-        ###################################################
-        # POSTing with wrong workspace_guid
-        ###################################################
-        
-        post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": "bleh-bad-workspace-guid"})
-        data = {"title": self.third_test_title, "content": self.third_test_content}
-        
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try POSTing with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
-        data = {"title": self.third_test_title, "content": self.third_test_content}
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_get_renkan(self):
-        
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        data = {}
-        post_response = self.client.post(post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        new_renkan_guid = post_response_dict.get("id", "")
-        new_revision_guid = post_response_dict.get("current_revision_id", "")
-        
-        ###################################################
-        # GETting posted Renkan
-        ###################################################
-        
-        get_url = reverse("renkan_detail", kwargs={"renkan_guid":new_renkan_guid})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        # Checking GET data
-        get_response_dict = json.loads(get_response.content.decode())
-        self.assertEqual(new_renkan_guid, get_response_dict.get("id", ""))
-        self.assertEqual(new_revision_guid, get_response_dict.get("current_revision_id", ""))
-        self.assertEqual(self.user.username, get_response_dict.get("created_by", ""))
-        self.assertEqual(self.user.username, get_response_dict.get("last_updated_by", ""))
-        
-        ###################################################
-        # GETting with wrong guid
-        ###################################################
-        
-        get_url = reverse("renkan_detail", kwargs={"renkan_guid": "bad-id"})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try GETting with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        get_url = reverse("renkan_detail", kwargs={"renkan_guid": new_renkan_guid})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
-    
-    def test_get_renkan_list(self):
-        
-        ###################################################
-        # POSTing new Renkans
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        first_data = {"title": self.first_test_title, "content": self.first_test_content}
-        second_data = {"title": self.second_test_title, "content": self.second_test_content}
-        self.client.post(post_url, first_data, format="json")
-        self.client.post(post_url, second_data, format="json")
-        
-        ###################################################
-        # GETting posted Renkans
-        ###################################################
-        
-        get_url = post_url
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        get_response_content = json.loads(get_response.content.decode()) # Should be a list
-        self.assertTrue(isinstance(get_response_content, list))
-        self.assertEqual(len(get_response_content), 2)
-        
-        ###################################################
-        # POSTing new Renkan into workspace
-        ###################################################
-        
-        post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
-        third_data = {"title": self.third_test_title, "content": self.third_test_content}
-        self.client.post(post_url, third_data, format="json")
-        
-        ###################################################
-        # GETting posted Renkans
-        ###################################################
-        
-        get_url = post_url
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        get_response_content = json.loads(get_response.content.decode()) # Should be a list
-        self.assertTrue(isinstance(get_response_content, list))
-        self.assertEqual(len(get_response_content), 1)
-        
-    
-    def test_update_renkan(self):
-
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        data = {}
-        post_response = self.client.post(post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        renkan_guid = post_response_dict.get("id", "")
-        revision_guid = post_response_dict.get("current_revision_id", "")
-
-        ###################################################
-        # PUTting by updating current revision
-        ###################################################
-        
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid": renkan_guid})
-        put_data = {
-            "title" : self.first_test_title,
-            "content" : self.first_test_content,
-            "create_new_revision": False
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_200_OK)
-        put_response_dict = json.loads(put_response.content.decode())
-        # checking we"re still on the same revision as the initial one
-        updated_project = Renkan.objects.get(renkan_guid=renkan_guid)
-        self.assertEqual(revision_guid, put_response_dict.get("current_revision_id", ""))
-        self.assertEqual(put_response_dict.get("revision_count", ""), 1)
-        self.assertEqual(updated_project.revision_count, 1)
-        
-        self.assertEqual(revision_guid, updated_project.current_revision_guid)
-        # checking data was updated
-        #     in the reponse
-        self.assertEqual(put_response_dict.get("title", ""), self.first_test_title)
-        self.assertEqual(put_response_dict.get("content", ""), self.first_test_content)
-        #     in the database
-        updated_revision = Revision.objects.get(revision_guid=revision_guid)
-        self.assertEqual(updated_revision.title, self.first_test_title)
-        self.assertEqual(updated_revision.content, self.first_test_content)
-        
-        ###################################################
-        # PUTting by creating a new revision
-        ###################################################
-        
-        put_data = {
-            "title" : self.second_test_title,
-            "content" : self.second_test_content,
-            "create_new_revision": True
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_200_OK)  
-        put_response_dict = json.loads(put_response.content.decode())
-        
-        # checking we created a new revision
-        updated_project = Renkan.objects.get(renkan_guid=renkan_guid)
-        created_revision_guid = put_response_dict.get("current_revision_id", "")
-        self.assertNotEqual(created_revision_guid, revision_guid)
-        self.assertEqual(Renkan.objects.count(), 1)
-        self.assertEqual(Revision.objects.count(), 2)
-        self.assertEqual(put_response_dict.get("revision_count", ""), 2)
-        self.assertEqual(updated_project.revision_count, 2)
-        
-        # checking project now points towards new revision
-        self.assertEqual(updated_project.current_revision_guid, created_revision_guid)
-        # checking data was updated
-        #     in the reponse
-        self.assertEqual(put_response_dict.get("title", ""), self.second_test_title)
-        self.assertEqual(put_response_dict.get("content", ""), self.second_test_content)
-        #     in the database
-        updated_revision = Revision.objects.get(revision_guid=created_revision_guid)
-        self.assertEqual(updated_revision.title, self.second_test_title)
-        self.assertEqual(updated_revision.content, self.second_test_content)
-        
-        ###################################################
-        # try to update source_revision_guid or workspace_guid
-        ###################################################
-        
-        put_data = {
-            "title" : self.third_test_title,
-            "content" : self.third_test_content,
-            "workspace_id": "bleh-workspace-guid" # should fail without even checking the id
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST)
-        
-        put_data = {
-            "title" : self.third_test_title,
-            "content" : self.third_test_content,
-            "source_revision_id" : "bleh-revision-guid" # should fail without even checking the id
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST)
-        
-        ###################################################
-        # PUTting with wrong guid
-        ###################################################
-        
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid": "bad-id"})
-        put_response = self.client.put(put_url, {}, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try PUTting with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid": renkan_guid})
-        put_data = {
-            "title" : self.third_test_title,
-            "content" : self.third_test_content,
-            "create_new_revision": False
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_copy_renkan(self):
-        
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        data = {"title": self.first_test_title, "content": self.first_test_content}
-        post_response = self.client.post(post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        source_renkan_guid = post_response_dict.get("id", "")
-        source_revision_guid = post_response_dict.get("current_revision_id", "")
-        
-        ###################################################
-        # POSTing copy with query arg for RENKAN guid
-        ###################################################
-        
-        first_copy_data = {"title": "new_title_copy_1"}
-        first_copy_url = post_url+"?source_renkan_id="+source_renkan_guid
-        first_copy_response = self.client.post(first_copy_url, first_copy_data, format="json")
-        first_copy_response_dict = json.loads(first_copy_response.content.decode())
-        self.assertEqual(first_copy_response.status_code, status.HTTP_201_CREATED)  
-        self.assertEqual(Renkan.objects.count(), 2)
-        # Checking data
-        #     in the response
-        self.assertNotEqual(first_copy_response_dict.get("id"), source_renkan_guid)
-        self.assertEqual(first_copy_response_dict.get("source_revision_id"), source_revision_guid)
-        self.assertEqual(first_copy_response_dict.get("title", ""), "new_title_copy_1")
-        self.assertEqual(first_copy_response_dict.get("content", ""), self.first_test_content)
-        self.assertEqual(first_copy_response_dict.get("created_by", ""), self.user.username)
-        self.assertEqual(first_copy_response_dict.get("last_updated_by", ""), self.user.username)
-        #     in the database
-        first_copy = Renkan.objects.get(renkan_guid=first_copy_response_dict.get("id", ""))
-        first_copy_revision = Revision.objects.get(revision_guid=first_copy_response_dict.get("current_revision_id", ""))
-        self.assertEqual(first_copy.title, "new_title_copy_1")
-        self.assertEqual(first_copy.content, self.first_test_content)
-        self.assertEqual(first_copy_revision.title, "new_title_copy_1")
-        self.assertEqual(first_copy_revision.content, self.first_test_content)
-        self.assertEqual(first_copy.creator.username, self.user.username)
-        self.assertEqual(first_copy_revision.creator.username, self.user.username)
-        self.assertEqual(first_copy_revision.last_updated_by.username, self.user.username)
-        
-        ###################################################
-        # POSTing copy with source_renkan_guid in data
-        ###################################################
-        
-        second_copy_data = {"source_renkan_id": source_renkan_guid, "title": "new_title_copy_2"}
-        second_copy_url = post_url
-        second_copy_response = self.client.post(second_copy_url, second_copy_data, format="json")
-        second_copy_response_dict = json.loads(second_copy_response.content.decode())
-        self.assertEqual(second_copy_response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(Renkan.objects.count(), 3)
-        # Checking data
-        #     in the response
-        self.assertNotEqual(second_copy_response_dict.get("id"), source_renkan_guid)
-        self.assertEqual(second_copy_response_dict.get("source_revision_id"), source_revision_guid)
-        self.assertEqual(second_copy_response_dict.get("title", ""), "new_title_copy_2")
-        self.assertEqual(second_copy_response_dict.get("content", ""), self.first_test_content)
-        self.assertEqual(second_copy_response_dict.get("created_by", ""), self.user.username)
-        self.assertEqual(second_copy_response_dict.get("last_updated_by", ""), self.user.username)
-        #     in the database
-        second_copy = Renkan.objects.get(renkan_guid=second_copy_response_dict.get("id", ""))
-        second_copy_revision = Revision.objects.get(revision_guid=second_copy_response_dict.get("current_revision_id", ""))
-        self.assertEqual(second_copy.title, "new_title_copy_2")
-        self.assertEqual(second_copy.content, self.first_test_content)
-        self.assertEqual(second_copy_revision.title, "new_title_copy_2")
-        self.assertEqual(second_copy_revision.content, self.first_test_content)
-        self.assertEqual(second_copy.creator.username, self.user.username)
-        self.assertEqual(second_copy_revision.creator.username, self.user.username)
-        self.assertEqual(second_copy_revision.last_updated_by.username, self.user.username)
-        
-        ###################################################
-        # POSTing copy with query arg for REVISION guid
-        ###################################################
-        
-        third_copy_data = {"title": "new_title_copy_3"}
-        third_copy_url = post_url+"?source_revision_id="+source_revision_guid
-        third_copy_response = self.client.post(third_copy_url, third_copy_data, format="json")
-        third_copy_response_dict = json.loads(third_copy_response.content.decode())
-        self.assertEqual(third_copy_response.status_code, status.HTTP_201_CREATED)  
-        self.assertEqual(Renkan.objects.count(), 4)
-        # Checking data
-        #     in the response
-        self.assertNotEqual(third_copy_response_dict.get("id"), source_renkan_guid)
-        self.assertEqual(third_copy_response_dict.get("source_revision_id"), source_revision_guid)
-        self.assertEqual(third_copy_response_dict.get("title", ""), "new_title_copy_3")
-        self.assertEqual(third_copy_response_dict.get("content", ""), self.first_test_content)
-        self.assertEqual(third_copy_response_dict.get("created_by", ""), self.user.username)
-        self.assertEqual(third_copy_response_dict.get("last_updated_by", ""), self.user.username)
-        #     in the database
-        third_copy = Renkan.objects.get(renkan_guid=third_copy_response_dict.get("id", ""))
-        third_copy_revision = Revision.objects.get(revision_guid=third_copy_response_dict.get("current_revision_id", ""))
-        self.assertEqual(third_copy.title, "new_title_copy_3")
-        self.assertEqual(third_copy.content, self.first_test_content)
-        self.assertEqual(third_copy_revision.title, "new_title_copy_3")
-        self.assertEqual(third_copy_revision.content, self.first_test_content)
-        self.assertEqual(third_copy.creator.username, self.user.username)
-        self.assertEqual(third_copy_revision.creator.username, self.user.username)
-        self.assertEqual(third_copy_revision.last_updated_by.username, self.user.username)
-        
-        ###################################################
-        # POSTing copy with source_revision_guid in data
-        ###################################################
-        
-        fourth_copy_data = {"source_revision_id": source_revision_guid}
-        fourth_copy_url = post_url
-        fourth_copy_response = self.client.post(fourth_copy_url, fourth_copy_data, format="json")
-        fourth_copy_response_dict = json.loads(fourth_copy_response.content.decode())
-        self.assertEqual(fourth_copy_response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(Renkan.objects.count(), 5)
-        # Checking data
-        #     in the response
-        self.assertNotEqual(fourth_copy_response_dict.get("id"), source_renkan_guid)
-        self.assertEqual(fourth_copy_response_dict.get("source_revision_id"), source_revision_guid)
-        self.assertEqual(fourth_copy_response_dict.get("title", ""), self.first_test_title)
-        self.assertEqual(fourth_copy_response_dict.get("content", ""), self.first_test_content)
-        self.assertEqual(fourth_copy_response_dict.get("created_by", ""), self.user.username)
-        self.assertEqual(fourth_copy_response_dict.get("last_updated_by", ""), self.user.username)
-        #     in the database
-        fourth_copy = Renkan.objects.get(renkan_guid=fourth_copy_response_dict.get("id", ""))
-        fourth_copy_revision = Revision.objects.get(revision_guid=fourth_copy_response_dict.get("current_revision_id", ""))
-        self.assertEqual(fourth_copy.title, self.first_test_title)
-        self.assertEqual(fourth_copy.content, self.first_test_content)
-        self.assertEqual(fourth_copy_revision.title, self.first_test_title)
-        self.assertEqual(fourth_copy_revision.content, self.first_test_content)
-        self.assertEqual(fourth_copy.creator.username, self.user.username)
-        self.assertEqual(fourth_copy_revision.creator.username, self.user.username)
-        self.assertEqual(fourth_copy_revision.last_updated_by.username, self.user.username)
-        
-        
-        ###################################################
-        # try POSTing copy with bad query arg guid and with bad data guid
-        ###################################################
-        
-        bad_copy_data = {"source_revision_id": "bleh_bad_id"}
-        #    with query arg
-        qarg_bad_copy_url = post_url+"?source_revision_id=bleh_bad_id"
-        qarg_bad_copy_response = self.client.post(qarg_bad_copy_url, {}, format="json")
-        self.assertEqual(qarg_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
-        #    with data
-        data_bad_copy_url = post_url
-        data_bad_copy_response = self.client.post(data_bad_copy_url, bad_copy_data, format="json")
-        self.assertEqual(data_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try POSTing with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        post_url = reverse("renkan_list")+"?source_revision_id="+source_revision_guid
-        post_response = self.client.post(post_url, {}, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_delete_renkan(self):
-        
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        data = {"title": self.first_test_title, "content": self.first_test_content}
-        post_response = self.client.post(post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        to_delete_renkan_guid = post_response_dict.get("id", "")
-        to_delete_revision_guid = post_response_dict.get("current_revision_id", "")
-        
-        ###################################################
-        # POSTing copy
-        ###################################################
-        data = {}
-        post_response = self.client.post(post_url+"?source="+to_delete_renkan_guid, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        copy_renkan_guid = post_response_dict.get("id", "")
-        
-        # Adding another revision
-        put_data = {"title": self.first_test_title+"_edited!", "create_new_revision": True}
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(Renkan.objects.count(), 2)
-        self.assertEqual(Revision.objects.count(), 3)
-        
-        ###################################################
-        # try to DELETE with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        delete_url = reverse("renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
-        delete_response = self.client.delete(delete_url, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-        # Restoring rightful user
-        _ = self.client.login(username="blop", password="blop")
-        
-        ###################################################
-        # DELETE initial renkan
-        ###################################################
-        
-        delete_url = reverse("renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
-        delete_response = self.client.delete(delete_url, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(Renkan.objects.count(), 1) # Only the copy remains
-        self.assertEqual(Revision.objects.count(), 1) # Only the copy initial revision remains
-        
-        ###################################################
-        # Try to GET deleted renkan
-        ###################################################
-        
-        get_url = reverse("renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # Check that remaining renkan doesn"t have reference to deleted renkan
-        ###################################################
-        copy_renkan = Renkan.objects.get(renkan_guid=copy_renkan_guid)
-        self.assertEqual(copy_renkan.source_revision_guid, "")
-        
-        ###################################################
-        # Try to DELETE renkan with wrong guid
-        ###################################################
-        
-        delete_url = reverse("renkan_detail", kwargs={"renkan_guid": "bad-id"})
-        delete_response = self.client.delete(delete_url, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
-    
\ No newline at end of file
--- a/server/python/django2/renkanmanager/tests/tests_revision.py	Tue Feb 02 16:21:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-import json
-
-from django.contrib.auth import get_user_model
-from django.core.urlresolvers import reverse
-from renkanmanager.models import Renkan, Revision
-from rest_framework import status
-from rest_framework.test import APITestCase
-
-
-User = get_user_model()
-
-class RevisionTests(APITestCase):
-    
-    def setUp(self):
-        User.objects.create_superuser("blop", "blop@blop.com", "blop")
-        self.user = User.objects.get(username="blop")
-        _ = self.client.login(username="blop", password="blop")
-        User.objects.create_user("blip", "blip@blip.com", "blip")
-        self.unauthorized_user = User.objects.get(username="blip")
-        
-        ###################################################
-        # POSTing new Renkan
-        ###################################################
-        
-        post_url = reverse("renkan_list")
-        self.test_title = "test_title_1"
-        self.test_content = json.dumps({
-            "id": "",
-            "title": "test_title_1",
-            "description": "test_description_1",
-            "created": "",
-            "updated": "",
-            "edges": [],
-            "nodes": [],
-            "users": [],
-            "space_id": "",
-            "views": []
-        })
-        self.test_data = {"title": self.test_title, "content": self.test_content}
-        post_response = self.client.post(post_url, self.test_data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        self.test_renkan_guid = post_response_dict.get("id", "")
-        self.test_initial_revision_guid = post_response_dict.get("current_revision_id", "")
-    
-    def test_get_revision(self):
-        
-        ###################################################
-        # GETting revision
-        ###################################################
-        
-        get_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        # Checking data
-        get_response_dict = json.loads(get_response.content.decode()) 
-        self.assertEqual(get_response_dict.get("id", ""), self.test_initial_revision_guid)
-        self.assertEqual(get_response_dict.get("parent_renkan_id", ""), self.test_renkan_guid)
-        self.assertEqual(get_response_dict.get("revision_created_by", ""), self.user.username)
-        self.assertEqual(get_response_dict.get("revision_last_updated_by", ""), self.user.username)
-        self.assertEqual(get_response_dict.get("title", ""), self.test_data.get("title", ""))
-        self.assertEqual(get_response_dict.get("content", ""), self.test_data.get("content", ""))
-        
-        ###################################################
-        # GETting with wrong renkan guid
-        ###################################################
-        
-        get_url = reverse("revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # GETting with wrong revision guid
-        ###################################################
-        
-        get_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try GETting with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        get_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_get_revision_list(self):
-        
-        ###################################################
-        # PUTting another revision in test renkan
-        ###################################################
-        
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid" : self.test_renkan_guid})
-        put_title = "edited_title"
-        put_data = {
-            "title" : put_title,
-            "create_new_revision": True
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        
-        ###################################################
-        # GETting revision lists
-        ###################################################
-        
-        get_url = reverse("revision_list", kwargs={"renkan_guid" : self.test_renkan_guid})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        # Checking data
-        get_response_content = json.loads(get_response.content.decode()) # Should be a list
-        self.assertTrue(isinstance(get_response_content, list))
-        self.assertEqual(len(get_response_content), 2)
-        
-        ###################################################
-        # GETting with wrong renkan guid
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        get_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        get_response = self.client.get(get_url, {}, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
-    
-    def test_delete_revision(self):
-        
-        ###################################################
-        # Try to DELETE the initial revision (last revision, should 400)
-        ###################################################
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
-        
-        ###################################################
-        # PUTting another revision in test renkan
-        ###################################################
-        
-        put_url = reverse("renkan_detail", kwargs={"renkan_guid" : self.test_renkan_guid})
-        put_title = "edited_title"
-        put_data = {
-            "title" : put_title,
-            "create_new_revision": True
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        put_response_dict = json.loads(put_response.content.decode())
-        added_revision_guid = put_response_dict.get("current_revision_id", "")
-        
-        ###################################################
-        # Try to DELETE the added revision (current revision, should 400)
-        ###################################################
-        
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": added_revision_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
-        
-        
-        ###################################################
-        # try to DELETE with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-        # Restoring rightful user
-        _ = self.client.login(username="blop", password="blop")
-        
-        ###################################################
-        # Try to DELETE the initial revision (should 204 now that we added a revision)
-        ###################################################
-        
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
-        
-        ###################################################
-        # Try to DELETE with wrong renkan guid
-        ###################################################
-        
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # Try to DELETE with wrong revision guid
-        ###################################################
-        
-        delete_url = reverse("revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
\ No newline at end of file
--- a/server/python/django2/renkanmanager/tests/tests_workspace.py	Tue Feb 02 16:21:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,254 +0,0 @@
-from django.core.urlresolvers import reverse
-from rest_framework.test import APITestCase
-from rest_framework import status
-from renkanmanager.models import Renkan, Workspace
-from django.contrib.auth import get_user_model
-import json
-
-User = get_user_model()
-
-class WorkspaceTests(APITestCase):
-    
-    def setUp(self):
-        User.objects.create_superuser("blop", "blop@blop.com", "blop")
-        self.user = User.objects.get(username="blop")
-        _ = self.client.login(username="blop", password="blop")
-        User.objects.create_user("blip", "blip@blip.com", "blip")
-        self.unauthorized_user = User.objects.get(username="blip")
-    
-    def test_post_workspace(self):
-        
-        ###################################################
-        # POSTing test Workspace
-        ###################################################
-        
-        post_url = reverse("workspace_list")
-        test_title = "bleh_title"
-        data = {"title": test_title}
-        post_response = self.client.post(post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
-        
-        # Checking data
-        #    in the response
-        self.assertEqual(post_response_dict.get("workspace_created_by"), self.user.username)
-        self.assertEqual(post_response_dict.get("title"), test_title)
-        #    in the database
-        created_workspace_guid = post_response_dict.get("id")
-        created_workspace = Workspace.objects.get(workspace_guid=created_workspace_guid)
-        self.assertEqual(created_workspace.creator.username, self.user.username)
-        self.assertEqual(created_workspace.title, test_title)
-        
-        # Checking permission
-        self.assertTrue(self.user.has_perm("view_workspace", created_workspace))
-        
-        ###################################################
-        # try POSTing with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        post_url = reverse("workspace_list")
-        data = {"title": "wont_work"}
-        post_response = self.client.post(post_url, data, format="json")
-        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-     
-    def test_get_workspace_list(self):
-        
-        ###################################################
-        # POSTing new Workspaces
-        ###################################################
-        
-        post_url = reverse("workspace_list")
-        first_test_title = "bleh_title_2"
-        second_test_title = "bleh_title_3"
-        first_data = {"title": first_test_title}
-        second_data = {"title": second_test_title}
-        self.client.post(post_url, first_data, format="json")
-        self.client.post(post_url, second_data, format="json")
-        
-        ###################################################
-        # GETting Workspaces
-        ###################################################
-        
-        get_url = post_url
-        get_response = self.client.get(get_url, format=json)
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        # Check data
-        get_response_content = json.loads(get_response.content.decode()) # Should be a list
-        self.assertTrue(isinstance(get_response_content, list))
-        self.assertEqual(len(get_response_content), 2)
-    
-    def test_get_workspace(self):
-        
-        ###################################################
-        # POSTing test Workspace
-        ###################################################
-        
-        workspace_post_url = reverse("workspace_list")
-        test_title = "bleh_title"
-        data = {"title": test_title}
-        post_response = self.client.post(workspace_post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        test_workspace_guid = post_response_dict.get("id")
-        
-        ###################################################
-        # POSTing new Renkans into test Workspace
-        ###################################################
-        
-        renkan_post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": test_workspace_guid})
-        first_test_title = "bleh_title"
-        second_test_title = "bleh_title_2"
-        first_data = {"title": first_test_title}
-        second_data = {"title": second_test_title}
-        self.client.post(renkan_post_url, first_data, format="json")
-        self.client.post(renkan_post_url, second_data, format="json")
-        
-        ###################################################
-        # GETting test Workspace
-        ###################################################
-        
-        get_url = reverse("workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
-        
-        # Checking GET data
-        get_response_dict = json.loads(get_response.content.decode())
-        self.assertEqual(test_workspace_guid, get_response_dict.get("id", ""))
-        self.assertEqual(test_title, get_response_dict.get("title", ""))
-        self.assertEqual(self.user.username, get_response_dict.get("workspace_created_by", ""))
-        self.assertEqual(2, get_response_dict.get("renkan_count"))
-        
-        ###################################################
-        # GETting wrong workspace_guid
-        ###################################################
-        
-        get_url = reverse("workspace_detail", kwargs={"workspace_guid": "bleh-bad-workspace-id"})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
-        
-        ###################################################
-        # try GETting with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        get_url = reverse("workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
-        get_response = self.client.get(get_url, format="json")
-        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_update_workspace(self):
-        
-        ###################################################
-        # POSTing test Workspace
-        ###################################################
-        
-        workspace_post_url = reverse("workspace_list")
-        test_title = "bleh_title"
-        data = {"title": test_title}
-        post_response = self.client.post(workspace_post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        test_workspace_guid = post_response_dict.get("id")
-        
-        ###################################################
-        # PUTting into test Workspace
-        ###################################################
-        
-        put_url = reverse("workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
-        edited_title = "bleh_title_2"
-        put_data = {"title": edited_title}
-        put_response = self.client.put(put_url, put_data, format="json")
-        put_response_dict = json.loads(put_response.content.decode())
-        self.assertEqual(put_response.status_code, status.HTTP_200_OK)
-        
-        # Checking data
-        #    in the response
-        self.assertEqual(put_response_dict.get("workspace_created_by"), self.user.username)
-        self.assertEqual(put_response_dict.get("title"), edited_title)
-        self.assertEqual(put_response_dict.get("renkan_count"), 0)
-        #    in the database
-        edited_workspace_guid = put_response_dict.get("id")
-        edited_workspace = Workspace.objects.get(workspace_guid=edited_workspace_guid)
-        self.assertEqual(edited_workspace.creator.username, self.user.username)
-        self.assertEqual(edited_workspace.title, edited_title)
-        self.assertEqual(edited_workspace.renkan_count, 0)
-        
-        ###################################################
-        # try PUTting with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        put_url = reverse("workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
-        put_data = {
-            "title" : "wont_work"
-        }
-        put_response = self.client.put(put_url, put_data, format="json")
-        self.assertEqual(put_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-    def test_delete_workspace(self):
-        
-        ###################################################
-        # POSTing test Workspace
-        ###################################################
-        
-        workspace_post_url = reverse("workspace_list")
-        test_title = "bleh_title"
-        data = {"title": test_title}
-        post_response = self.client.post(workspace_post_url, data, format="json")
-        post_response_dict = json.loads(post_response.content.decode())
-        to_delete_workspace_guid = post_response_dict.get("id", "")
-        
-        ###################################################
-        # POSTing new Renkan into test Workspace
-        ###################################################
-        
-        renkan_post_url = reverse("renkan_list_workspace", kwargs={"workspace_guid": to_delete_workspace_guid})
-        renkan_test_title = "bleh_title"
-        renkan_test_data = {"title": renkan_test_title}
-        post_response = self.client.post(renkan_post_url, renkan_test_data, format="json")
-        to_delete_renkan_guid = json.loads(post_response.content.decode()).get("id", "")
-        
-        ###################################################
-        # Try to DELETE test Workspace (should fail, it still contains a Renkan)
-        ###################################################
-        
-        delete_url = reverse("workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
-        
-        ###################################################
-        # DELETE posted Renkan
-        ###################################################
-        
-        delete_renkan_url = reverse("renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
-        delete_renkan_response = self.client.delete(delete_renkan_url, format="json")
-        self.assertEqual(delete_renkan_response.status_code, status.HTTP_204_NO_CONTENT)
-        
-        ###################################################
-        # try to DELETE with unauthorized user 
-        ###################################################
-        
-        _ = self.client.login(username="blip", password="blip")
-        delete_url = reverse("workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
-        
-        # Restoring rightful user
-        _ = self.client.login(username="blop", password="blop")
-        
-        ###################################################
-        # DELETE test Workspace 
-        ###################################################
-        
-        delete_url = reverse("workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
-        delete_response = self.client.delete(delete_url, {}, format="json")
-        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
-        
-        ###################################################
-        # Try to DELETE workspace with wrong guid
-        ###################################################
-        
-        delete_url = reverse("workspace_detail", kwargs={"workspace_guid": "bad-workspace-guid"})
-        delete_response = self.client.delete(delete_url, format=json)
-        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
-        
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_renkan.py	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,604 @@
+import json
+
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import reverse
+from renkanmanager.models import Renkan, Revision
+from rest_framework import status
+from rest_framework.test import APITestCase
+
+
+User = get_user_model()
+
+class RenkanTests(APITestCase):
+    
+    def setUp(self):
+        User.objects.create_superuser("blop", "blop@blop.com", "blop")
+        self.user = User.objects.get(username="blop")
+        _ = self.client.login(username="blop", password="blop")
+        User.objects.create_user("blip", "blip@blip.com", "blip")
+        self.unauthorized_user = User.objects.get(username="blip")
+        
+        # POSTing test workspace
+        post_url = reverse("v1.0:workspace_list")
+        test_title = "bleh_title"
+        data = {"title": test_title}
+        post_response = self.client.post(post_url, data, format="json")
+        self.test_workspace_guid = json.loads(post_response.content.decode()).get("id")
+        self.first_test_title = "test_title_1"
+        self.first_test_content = json.dumps({
+            "id": "",
+            "title": "test_title_1",
+            "description": "test_description_1",
+            "created": "",
+            "updated": "",
+            "edges": [],
+            "nodes": [],
+            "users": [],
+            "space_id": "",
+            "views": []
+        })
+        self.second_test_title = "test_title_1"
+        self.second_test_content = json.dumps({
+            "id": "",
+            "title": "test_title_2",
+            "description": "test_description_2",
+            "created": "",
+            "updated": "",
+            "edges": [],
+            "nodes": [],
+            "users": [],
+            "space_id": "",
+            "views": []
+        })
+        self.third_test_title = "test_title_1"
+        self.third_test_content = json.dumps({
+            "id": "",
+            "title": "test_title_3",
+            "description": "test_description_3",
+            "created": "",
+            "updated": "",
+            "edges": [],
+            "nodes": [],
+            "users": [],
+            "space_id": "",
+            "views": []
+        })
+        
+    def test_create_renkan(self):
+        
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        data = {"title": self.first_test_title, "content": self.first_test_content}
+        
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(Renkan.objects.count(), 1)
+        self.assertEqual(Revision.objects.count(), 1)
+        
+        # Checking POSTed Renkan data
+        post_response_dict = json.loads(post_response.content.decode())
+        new_renkan_guid = post_response_dict.get("id", "")
+        new_renkan = Renkan.objects.get(renkan_guid=new_renkan_guid)
+        # GUIDs and username
+        self.assertEqual(self.user.username, post_response_dict.get("created_by", ""))
+        self.assertEqual(self.user.username, post_response_dict.get("last_updated_by", ""))
+        self.assertEqual(self.user.username, new_renkan.creator.username)
+        # Content and title extracted from revision
+        self.assertEqual(new_renkan.title, self.first_test_title)
+        self.assertEqual(new_renkan.content, self.first_test_content)
+        # Revision count
+        self.assertEqual(new_renkan.revision_count, 1)
+        self.assertEqual(post_response_dict.get("revision_count", ""), 1)
+        
+        # Checking POSTed Renkan revision data
+        new_renkan_revision_guid = post_response_dict.get("current_revision_id", "")
+        new_renkan_revision = Revision.objects.get(revision_guid = new_renkan_revision_guid)
+        # GUIDs and username
+        self.assertEqual(self.user.username, new_renkan_revision.creator.username)
+        self.assertEqual(self.user.username, new_renkan_revision.last_updated_by.username)
+        # Title and content
+        #    in the response
+        self.assertEqual(post_response_dict.get("title", ""), self.first_test_title)
+        self.assertEqual(post_response_dict.get("content", ""), self.first_test_content)
+        #    in the database
+        self.assertEqual(new_renkan_revision.title, self.first_test_title)
+        self.assertEqual(new_renkan_revision.content, self.first_test_content)
+        
+        # Checking permission
+        self.assertTrue(self.user.has_perm("view_renkan", new_renkan))
+        
+        ###################################################
+        # POSTing new Renkan into workspace
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
+        data = {"title": self.second_test_title, "content": self.second_test_content}
+        
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(Renkan.objects.count(), 2)
+        self.assertEqual(Revision.objects.count(), 2)
+        
+        # Checking POSTed Renkan data
+        post_response_dict = json.loads(post_response.content.decode())
+        new_renkan_guid = post_response_dict.get("id", "")
+        new_renkan = Renkan.objects.get(renkan_guid=new_renkan_guid)
+        # GUIDs and username
+        self.assertEqual(self.test_workspace_guid, post_response_dict.get("workspace_id", ""))
+        self.assertEqual(self.test_workspace_guid, new_renkan.workspace_guid)
+        self.assertEqual(self.user.username, post_response_dict.get("created_by", ""))
+        self.assertEqual(self.user.username, post_response_dict.get("last_updated_by", ""))
+        self.assertEqual(self.user.username, new_renkan.creator.username)
+        # Content and title extracted from revision
+        self.assertEqual(new_renkan.title, self.second_test_title)
+        self.assertEqual(new_renkan.content, self.second_test_content)
+        # Revision count
+        self.assertEqual(new_renkan.revision_count, 1)
+        self.assertEqual(post_response_dict.get("revision_count", ""), 1)
+        
+        ###################################################
+        # POSTing new Renkan with no content
+        ###################################################
+        post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
+        test_title = "bleh_title_2"
+        data = {"title": test_title}
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(Renkan.objects.count(), 3)
+        self.assertEqual(Revision.objects.count(), 3)
+        
+        ###################################################
+        # POSTing with wrong workspace_guid
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": "bleh-bad-workspace-guid"})
+        data = {"title": self.third_test_title, "content": self.third_test_content}
+        
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try POSTing with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
+        data = {"title": self.third_test_title, "content": self.third_test_content}
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_get_renkan(self):
+        
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        data = {}
+        post_response = self.client.post(post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        new_renkan_guid = post_response_dict.get("id", "")
+        new_revision_guid = post_response_dict.get("current_revision_id", "")
+        
+        ###################################################
+        # GETting posted Renkan
+        ###################################################
+        
+        get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":new_renkan_guid})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        # Checking GET data
+        get_response_dict = json.loads(get_response.content.decode())
+        self.assertEqual(new_renkan_guid, get_response_dict.get("id", ""))
+        self.assertEqual(new_revision_guid, get_response_dict.get("current_revision_id", ""))
+        self.assertEqual(self.user.username, get_response_dict.get("created_by", ""))
+        self.assertEqual(self.user.username, get_response_dict.get("last_updated_by", ""))
+        
+        ###################################################
+        # GETting with wrong guid
+        ###################################################
+        
+        get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try GETting with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": new_renkan_guid})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
+    
+    def test_get_renkan_list(self):
+        
+        ###################################################
+        # POSTing new Renkans
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        first_data = {"title": self.first_test_title, "content": self.first_test_content}
+        second_data = {"title": self.second_test_title, "content": self.second_test_content}
+        self.client.post(post_url, first_data, format="json")
+        self.client.post(post_url, second_data, format="json")
+        
+        ###################################################
+        # GETting posted Renkans
+        ###################################################
+        
+        get_url = post_url
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        get_response_content = json.loads(get_response.content.decode()) # Should be a list
+        self.assertTrue(isinstance(get_response_content, list))
+        self.assertEqual(len(get_response_content), 2)
+        
+        ###################################################
+        # POSTing new Renkan into workspace
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": self.test_workspace_guid})
+        third_data = {"title": self.third_test_title, "content": self.third_test_content}
+        self.client.post(post_url, third_data, format="json")
+        
+        ###################################################
+        # GETting posted Renkans
+        ###################################################
+        
+        get_url = post_url
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        get_response_content = json.loads(get_response.content.decode()) # Should be a list
+        self.assertTrue(isinstance(get_response_content, list))
+        self.assertEqual(len(get_response_content), 1)
+        
+    
+    def test_update_renkan(self):
+
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        data = {}
+        post_response = self.client.post(post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        renkan_guid = post_response_dict.get("id", "")
+        revision_guid = post_response_dict.get("current_revision_id", "")
+
+        ###################################################
+        # PUTting by updating current revision
+        ###################################################
+        
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": renkan_guid})
+        put_data = {
+            "title" : self.first_test_title,
+            "content" : self.first_test_content,
+            "create_new_revision": False
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_200_OK)
+        put_response_dict = json.loads(put_response.content.decode())
+        # checking we"re still on the same revision as the initial one
+        updated_project = Renkan.objects.get(renkan_guid=renkan_guid)
+        self.assertEqual(revision_guid, put_response_dict.get("current_revision_id", ""))
+        self.assertEqual(put_response_dict.get("revision_count", ""), 1)
+        self.assertEqual(updated_project.revision_count, 1)
+        
+        self.assertEqual(revision_guid, updated_project.current_revision_guid)
+        # checking data was updated
+        #     in the reponse
+        self.assertEqual(put_response_dict.get("title", ""), self.first_test_title)
+        self.assertEqual(put_response_dict.get("content", ""), self.first_test_content)
+        #     in the database
+        updated_revision = Revision.objects.get(revision_guid=revision_guid)
+        self.assertEqual(updated_revision.title, self.first_test_title)
+        self.assertEqual(updated_revision.content, self.first_test_content)
+        
+        ###################################################
+        # PUTting by creating a new revision
+        ###################################################
+        
+        put_data = {
+            "title" : self.second_test_title,
+            "content" : self.second_test_content,
+            "create_new_revision": True
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_200_OK)  
+        put_response_dict = json.loads(put_response.content.decode())
+        
+        # checking we created a new revision
+        updated_project = Renkan.objects.get(renkan_guid=renkan_guid)
+        created_revision_guid = put_response_dict.get("current_revision_id", "")
+        self.assertNotEqual(created_revision_guid, revision_guid)
+        self.assertEqual(Renkan.objects.count(), 1)
+        self.assertEqual(Revision.objects.count(), 2)
+        self.assertEqual(put_response_dict.get("revision_count", ""), 2)
+        self.assertEqual(updated_project.revision_count, 2)
+        
+        # checking project now points towards new revision
+        self.assertEqual(updated_project.current_revision_guid, created_revision_guid)
+        # checking data was updated
+        #     in the reponse
+        self.assertEqual(put_response_dict.get("title", ""), self.second_test_title)
+        self.assertEqual(put_response_dict.get("content", ""), self.second_test_content)
+        #     in the database
+        updated_revision = Revision.objects.get(revision_guid=created_revision_guid)
+        self.assertEqual(updated_revision.title, self.second_test_title)
+        self.assertEqual(updated_revision.content, self.second_test_content)
+        
+        ###################################################
+        # try to update source_revision_guid or workspace_guid
+        ###################################################
+        
+        put_data = {
+            "title" : self.third_test_title,
+            "content" : self.third_test_content,
+            "workspace_id": "bleh-workspace-guid" # should fail without even checking the id
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST)
+        
+        put_data = {
+            "title" : self.third_test_title,
+            "content" : self.third_test_content,
+            "source_revision_id" : "bleh-revision-guid" # should fail without even checking the id
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST)
+        
+        ###################################################
+        # PUTting with wrong guid
+        ###################################################
+        
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
+        put_response = self.client.put(put_url, {}, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try PUTting with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": renkan_guid})
+        put_data = {
+            "title" : self.third_test_title,
+            "content" : self.third_test_content,
+            "create_new_revision": False
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_copy_renkan(self):
+        
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        data = {"title": self.first_test_title, "content": self.first_test_content}
+        post_response = self.client.post(post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        source_renkan_guid = post_response_dict.get("id", "")
+        source_revision_guid = post_response_dict.get("current_revision_id", "")
+        
+        ###################################################
+        # POSTing copy with query arg for RENKAN guid
+        ###################################################
+        
+        first_copy_data = {"title": "new_title_copy_1"}
+        first_copy_url = post_url+"?source_renkan_id="+source_renkan_guid
+        first_copy_response = self.client.post(first_copy_url, first_copy_data, format="json")
+        first_copy_response_dict = json.loads(first_copy_response.content.decode())
+        self.assertEqual(first_copy_response.status_code, status.HTTP_201_CREATED)  
+        self.assertEqual(Renkan.objects.count(), 2)
+        # Checking data
+        #     in the response
+        self.assertNotEqual(first_copy_response_dict.get("id"), source_renkan_guid)
+        self.assertEqual(first_copy_response_dict.get("source_revision_id"), source_revision_guid)
+        self.assertEqual(first_copy_response_dict.get("title", ""), "new_title_copy_1")
+        self.assertEqual(first_copy_response_dict.get("content", ""), self.first_test_content)
+        self.assertEqual(first_copy_response_dict.get("created_by", ""), self.user.username)
+        self.assertEqual(first_copy_response_dict.get("last_updated_by", ""), self.user.username)
+        #     in the database
+        first_copy = Renkan.objects.get(renkan_guid=first_copy_response_dict.get("id", ""))
+        first_copy_revision = Revision.objects.get(revision_guid=first_copy_response_dict.get("current_revision_id", ""))
+        self.assertEqual(first_copy.title, "new_title_copy_1")
+        self.assertEqual(first_copy.content, self.first_test_content)
+        self.assertEqual(first_copy_revision.title, "new_title_copy_1")
+        self.assertEqual(first_copy_revision.content, self.first_test_content)
+        self.assertEqual(first_copy.creator.username, self.user.username)
+        self.assertEqual(first_copy_revision.creator.username, self.user.username)
+        self.assertEqual(first_copy_revision.last_updated_by.username, self.user.username)
+        
+        ###################################################
+        # POSTing copy with source_renkan_guid in data
+        ###################################################
+        
+        second_copy_data = {"source_renkan_id": source_renkan_guid, "title": "new_title_copy_2"}
+        second_copy_url = post_url
+        second_copy_response = self.client.post(second_copy_url, second_copy_data, format="json")
+        second_copy_response_dict = json.loads(second_copy_response.content.decode())
+        self.assertEqual(second_copy_response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(Renkan.objects.count(), 3)
+        # Checking data
+        #     in the response
+        self.assertNotEqual(second_copy_response_dict.get("id"), source_renkan_guid)
+        self.assertEqual(second_copy_response_dict.get("source_revision_id"), source_revision_guid)
+        self.assertEqual(second_copy_response_dict.get("title", ""), "new_title_copy_2")
+        self.assertEqual(second_copy_response_dict.get("content", ""), self.first_test_content)
+        self.assertEqual(second_copy_response_dict.get("created_by", ""), self.user.username)
+        self.assertEqual(second_copy_response_dict.get("last_updated_by", ""), self.user.username)
+        #     in the database
+        second_copy = Renkan.objects.get(renkan_guid=second_copy_response_dict.get("id", ""))
+        second_copy_revision = Revision.objects.get(revision_guid=second_copy_response_dict.get("current_revision_id", ""))
+        self.assertEqual(second_copy.title, "new_title_copy_2")
+        self.assertEqual(second_copy.content, self.first_test_content)
+        self.assertEqual(second_copy_revision.title, "new_title_copy_2")
+        self.assertEqual(second_copy_revision.content, self.first_test_content)
+        self.assertEqual(second_copy.creator.username, self.user.username)
+        self.assertEqual(second_copy_revision.creator.username, self.user.username)
+        self.assertEqual(second_copy_revision.last_updated_by.username, self.user.username)
+        
+        ###################################################
+        # POSTing copy with query arg for REVISION guid
+        ###################################################
+        
+        third_copy_data = {"title": "new_title_copy_3"}
+        third_copy_url = post_url+"?source_revision_id="+source_revision_guid
+        third_copy_response = self.client.post(third_copy_url, third_copy_data, format="json")
+        third_copy_response_dict = json.loads(third_copy_response.content.decode())
+        self.assertEqual(third_copy_response.status_code, status.HTTP_201_CREATED)  
+        self.assertEqual(Renkan.objects.count(), 4)
+        # Checking data
+        #     in the response
+        self.assertNotEqual(third_copy_response_dict.get("id"), source_renkan_guid)
+        self.assertEqual(third_copy_response_dict.get("source_revision_id"), source_revision_guid)
+        self.assertEqual(third_copy_response_dict.get("title", ""), "new_title_copy_3")
+        self.assertEqual(third_copy_response_dict.get("content", ""), self.first_test_content)
+        self.assertEqual(third_copy_response_dict.get("created_by", ""), self.user.username)
+        self.assertEqual(third_copy_response_dict.get("last_updated_by", ""), self.user.username)
+        #     in the database
+        third_copy = Renkan.objects.get(renkan_guid=third_copy_response_dict.get("id", ""))
+        third_copy_revision = Revision.objects.get(revision_guid=third_copy_response_dict.get("current_revision_id", ""))
+        self.assertEqual(third_copy.title, "new_title_copy_3")
+        self.assertEqual(third_copy.content, self.first_test_content)
+        self.assertEqual(third_copy_revision.title, "new_title_copy_3")
+        self.assertEqual(third_copy_revision.content, self.first_test_content)
+        self.assertEqual(third_copy.creator.username, self.user.username)
+        self.assertEqual(third_copy_revision.creator.username, self.user.username)
+        self.assertEqual(third_copy_revision.last_updated_by.username, self.user.username)
+        
+        ###################################################
+        # POSTing copy with source_revision_guid in data
+        ###################################################
+        
+        fourth_copy_data = {"source_revision_id": source_revision_guid}
+        fourth_copy_url = post_url
+        fourth_copy_response = self.client.post(fourth_copy_url, fourth_copy_data, format="json")
+        fourth_copy_response_dict = json.loads(fourth_copy_response.content.decode())
+        self.assertEqual(fourth_copy_response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(Renkan.objects.count(), 5)
+        # Checking data
+        #     in the response
+        self.assertNotEqual(fourth_copy_response_dict.get("id"), source_renkan_guid)
+        self.assertEqual(fourth_copy_response_dict.get("source_revision_id"), source_revision_guid)
+        self.assertEqual(fourth_copy_response_dict.get("title", ""), self.first_test_title)
+        self.assertEqual(fourth_copy_response_dict.get("content", ""), self.first_test_content)
+        self.assertEqual(fourth_copy_response_dict.get("created_by", ""), self.user.username)
+        self.assertEqual(fourth_copy_response_dict.get("last_updated_by", ""), self.user.username)
+        #     in the database
+        fourth_copy = Renkan.objects.get(renkan_guid=fourth_copy_response_dict.get("id", ""))
+        fourth_copy_revision = Revision.objects.get(revision_guid=fourth_copy_response_dict.get("current_revision_id", ""))
+        self.assertEqual(fourth_copy.title, self.first_test_title)
+        self.assertEqual(fourth_copy.content, self.first_test_content)
+        self.assertEqual(fourth_copy_revision.title, self.first_test_title)
+        self.assertEqual(fourth_copy_revision.content, self.first_test_content)
+        self.assertEqual(fourth_copy.creator.username, self.user.username)
+        self.assertEqual(fourth_copy_revision.creator.username, self.user.username)
+        self.assertEqual(fourth_copy_revision.last_updated_by.username, self.user.username)
+        
+        
+        ###################################################
+        # try POSTing copy with bad query arg guid and with bad data guid
+        ###################################################
+        
+        bad_copy_data = {"source_revision_id": "bleh_bad_id"}
+        #    with query arg
+        qarg_bad_copy_url = post_url+"?source_revision_id=bleh_bad_id"
+        qarg_bad_copy_response = self.client.post(qarg_bad_copy_url, {}, format="json")
+        self.assertEqual(qarg_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
+        #    with data
+        data_bad_copy_url = post_url
+        data_bad_copy_response = self.client.post(data_bad_copy_url, bad_copy_data, format="json")
+        self.assertEqual(data_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try POSTing with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        post_url = reverse("v1.0:renkan_list")+"?source_revision_id="+source_revision_guid
+        post_response = self.client.post(post_url, {}, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_delete_renkan(self):
+        
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        data = {"title": self.first_test_title, "content": self.first_test_content}
+        post_response = self.client.post(post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        to_delete_renkan_guid = post_response_dict.get("id", "")
+        to_delete_revision_guid = post_response_dict.get("current_revision_id", "")
+        
+        ###################################################
+        # POSTing copy
+        ###################################################
+        data = {}
+        post_response = self.client.post(post_url+"?source="+to_delete_renkan_guid, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        copy_renkan_guid = post_response_dict.get("id", "")
+        
+        # Adding another revision
+        put_data = {"title": self.first_test_title+"_edited!", "create_new_revision": True}
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(Renkan.objects.count(), 2)
+        self.assertEqual(Revision.objects.count(), 3)
+        
+        ###################################################
+        # try to DELETE with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        delete_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
+        delete_response = self.client.delete(delete_url, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+        # Restoring rightful user
+        _ = self.client.login(username="blop", password="blop")
+        
+        ###################################################
+        # DELETE initial renkan
+        ###################################################
+        
+        delete_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
+        delete_response = self.client.delete(delete_url, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
+        self.assertEqual(Renkan.objects.count(), 1) # Only the copy remains
+        self.assertEqual(Revision.objects.count(), 1) # Only the copy initial revision remains
+        
+        ###################################################
+        # Try to GET deleted renkan
+        ###################################################
+        
+        get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # Check that remaining renkan doesn"t have reference to deleted renkan
+        ###################################################
+        copy_renkan = Renkan.objects.get(renkan_guid=copy_renkan_guid)
+        self.assertEqual(copy_renkan.source_revision_guid, "")
+        
+        ###################################################
+        # Try to DELETE renkan with wrong guid
+        ###################################################
+        
+        delete_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
+        delete_response = self.client.delete(delete_url, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
+    
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_revision.py	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,191 @@
+import json
+
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import reverse
+from renkanmanager.models import Renkan, Revision
+from rest_framework import status
+from rest_framework.test import APITestCase
+
+
+User = get_user_model()
+
+class RevisionTests(APITestCase):
+    
+    def setUp(self):
+        User.objects.create_superuser("blop", "blop@blop.com", "blop")
+        self.user = User.objects.get(username="blop")
+        _ = self.client.login(username="blop", password="blop")
+        User.objects.create_user("blip", "blip@blip.com", "blip")
+        self.unauthorized_user = User.objects.get(username="blip")
+        
+        ###################################################
+        # POSTing new Renkan
+        ###################################################
+        
+        post_url = reverse("v1.0:renkan_list")
+        self.test_title = "test_title_1"
+        self.test_content = json.dumps({
+            "id": "",
+            "title": "test_title_1",
+            "description": "test_description_1",
+            "created": "",
+            "updated": "",
+            "edges": [],
+            "nodes": [],
+            "users": [],
+            "space_id": "",
+            "views": []
+        })
+        self.test_data = {"title": self.test_title, "content": self.test_content}
+        post_response = self.client.post(post_url, self.test_data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        self.test_renkan_guid = post_response_dict.get("id", "")
+        self.test_initial_revision_guid = post_response_dict.get("current_revision_id", "")
+    
+    def test_get_revision(self):
+        
+        ###################################################
+        # GETting revision
+        ###################################################
+        
+        get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        # Checking data
+        get_response_dict = json.loads(get_response.content.decode()) 
+        self.assertEqual(get_response_dict.get("id", ""), self.test_initial_revision_guid)
+        self.assertEqual(get_response_dict.get("parent_renkan_id", ""), self.test_renkan_guid)
+        self.assertEqual(get_response_dict.get("revision_created_by", ""), self.user.username)
+        self.assertEqual(get_response_dict.get("revision_last_updated_by", ""), self.user.username)
+        self.assertEqual(get_response_dict.get("title", ""), self.test_data.get("title", ""))
+        self.assertEqual(get_response_dict.get("content", ""), self.test_data.get("content", ""))
+        
+        ###################################################
+        # GETting with wrong renkan guid
+        ###################################################
+        
+        get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # GETting with wrong revision guid
+        ###################################################
+        
+        get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try GETting with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_get_revision_list(self):
+        
+        ###################################################
+        # PUTting another revision in test renkan
+        ###################################################
+        
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid" : self.test_renkan_guid})
+        put_title = "edited_title"
+        put_data = {
+            "title" : put_title,
+            "create_new_revision": True
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        
+        ###################################################
+        # GETting revision lists
+        ###################################################
+        
+        get_url = reverse("v1.0:revision_list", kwargs={"renkan_guid" : self.test_renkan_guid})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        # Checking data
+        get_response_content = json.loads(get_response.content.decode()) # Should be a list
+        self.assertTrue(isinstance(get_response_content, list))
+        self.assertEqual(len(get_response_content), 2)
+        
+        ###################################################
+        # GETting with wrong renkan guid
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        get_response = self.client.get(get_url, {}, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
+    
+    def test_delete_revision(self):
+        
+        ###################################################
+        # Try to DELETE the initial revision (last revision, should 400)
+        ###################################################
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
+        
+        ###################################################
+        # PUTting another revision in test renkan
+        ###################################################
+        
+        put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid" : self.test_renkan_guid})
+        put_title = "edited_title"
+        put_data = {
+            "title" : put_title,
+            "create_new_revision": True
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        put_response_dict = json.loads(put_response.content.decode())
+        added_revision_guid = put_response_dict.get("current_revision_id", "")
+        
+        ###################################################
+        # Try to DELETE the added revision (current revision, should 400)
+        ###################################################
+        
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": added_revision_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
+        
+        
+        ###################################################
+        # try to DELETE with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+        # Restoring rightful user
+        _ = self.client.login(username="blop", password="blop")
+        
+        ###################################################
+        # Try to DELETE the initial revision (should 204 now that we added a revision)
+        ###################################################
+        
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": self.test_initial_revision_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
+        
+        ###################################################
+        # Try to DELETE with wrong renkan guid
+        ###################################################
+        
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # Try to DELETE with wrong revision guid
+        ###################################################
+        
+        delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_workspace.py	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,254 @@
+from django.core.urlresolvers import reverse
+from rest_framework.test import APITestCase
+from rest_framework import status
+from renkanmanager.models import Renkan, Workspace
+from django.contrib.auth import get_user_model
+import json
+
+User = get_user_model()
+
+class WorkspaceTests(APITestCase):
+    
+    def setUp(self):
+        User.objects.create_superuser("blop", "blop@blop.com", "blop")
+        self.user = User.objects.get(username="blop")
+        _ = self.client.login(username="blop", password="blop")
+        User.objects.create_user("blip", "blip@blip.com", "blip")
+        self.unauthorized_user = User.objects.get(username="blip")
+    
+    def test_post_workspace(self):
+        
+        ###################################################
+        # POSTing test Workspace
+        ###################################################
+        
+        post_url = reverse("v1.0:workspace_list")
+        test_title = "bleh_title"
+        data = {"title": test_title}
+        post_response = self.client.post(post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED)
+        
+        # Checking data
+        #    in the response
+        self.assertEqual(post_response_dict.get("workspace_created_by"), self.user.username)
+        self.assertEqual(post_response_dict.get("title"), test_title)
+        #    in the database
+        created_workspace_guid = post_response_dict.get("id")
+        created_workspace = Workspace.objects.get(workspace_guid=created_workspace_guid)
+        self.assertEqual(created_workspace.creator.username, self.user.username)
+        self.assertEqual(created_workspace.title, test_title)
+        
+        # Checking permission
+        self.assertTrue(self.user.has_perm("view_workspace", created_workspace))
+        
+        ###################################################
+        # try POSTing with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        post_url = reverse("v1.0:workspace_list")
+        data = {"title": "wont_work"}
+        post_response = self.client.post(post_url, data, format="json")
+        self.assertEqual(post_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+     
+    def test_get_workspace_list(self):
+        
+        ###################################################
+        # POSTing new Workspaces
+        ###################################################
+        
+        post_url = reverse("v1.0:workspace_list")
+        first_test_title = "bleh_title_2"
+        second_test_title = "bleh_title_3"
+        first_data = {"title": first_test_title}
+        second_data = {"title": second_test_title}
+        self.client.post(post_url, first_data, format="json")
+        self.client.post(post_url, second_data, format="json")
+        
+        ###################################################
+        # GETting Workspaces
+        ###################################################
+        
+        get_url = post_url
+        get_response = self.client.get(get_url, format=json)
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        # Check data
+        get_response_content = json.loads(get_response.content.decode()) # Should be a list
+        self.assertTrue(isinstance(get_response_content, list))
+        self.assertEqual(len(get_response_content), 2)
+    
+    def test_get_workspace(self):
+        
+        ###################################################
+        # POSTing test Workspace
+        ###################################################
+        
+        workspace_post_url = reverse("v1.0:workspace_list")
+        test_title = "bleh_title"
+        data = {"title": test_title}
+        post_response = self.client.post(workspace_post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        test_workspace_guid = post_response_dict.get("id")
+        
+        ###################################################
+        # POSTing new Renkans into test Workspace
+        ###################################################
+        
+        renkan_post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": test_workspace_guid})
+        first_test_title = "bleh_title"
+        second_test_title = "bleh_title_2"
+        first_data = {"title": first_test_title}
+        second_data = {"title": second_test_title}
+        self.client.post(renkan_post_url, first_data, format="json")
+        self.client.post(renkan_post_url, second_data, format="json")
+        
+        ###################################################
+        # GETting test Workspace
+        ###################################################
+        
+        get_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_200_OK)
+        
+        # Checking GET data
+        get_response_dict = json.loads(get_response.content.decode())
+        self.assertEqual(test_workspace_guid, get_response_dict.get("id", ""))
+        self.assertEqual(test_title, get_response_dict.get("title", ""))
+        self.assertEqual(self.user.username, get_response_dict.get("workspace_created_by", ""))
+        self.assertEqual(2, get_response_dict.get("renkan_count"))
+        
+        ###################################################
+        # GETting wrong workspace_guid
+        ###################################################
+        
+        get_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "bleh-bad-workspace-id"})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+        
+        ###################################################
+        # try GETting with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        get_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
+        get_response = self.client.get(get_url, format="json")
+        self.assertEqual(get_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_update_workspace(self):
+        
+        ###################################################
+        # POSTing test Workspace
+        ###################################################
+        
+        workspace_post_url = reverse("v1.0:workspace_list")
+        test_title = "bleh_title"
+        data = {"title": test_title}
+        post_response = self.client.post(workspace_post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        test_workspace_guid = post_response_dict.get("id")
+        
+        ###################################################
+        # PUTting into test Workspace
+        ###################################################
+        
+        put_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
+        edited_title = "bleh_title_2"
+        put_data = {"title": edited_title}
+        put_response = self.client.put(put_url, put_data, format="json")
+        put_response_dict = json.loads(put_response.content.decode())
+        self.assertEqual(put_response.status_code, status.HTTP_200_OK)
+        
+        # Checking data
+        #    in the response
+        self.assertEqual(put_response_dict.get("workspace_created_by"), self.user.username)
+        self.assertEqual(put_response_dict.get("title"), edited_title)
+        self.assertEqual(put_response_dict.get("renkan_count"), 0)
+        #    in the database
+        edited_workspace_guid = put_response_dict.get("id")
+        edited_workspace = Workspace.objects.get(workspace_guid=edited_workspace_guid)
+        self.assertEqual(edited_workspace.creator.username, self.user.username)
+        self.assertEqual(edited_workspace.title, edited_title)
+        self.assertEqual(edited_workspace.renkan_count, 0)
+        
+        ###################################################
+        # try PUTting with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        put_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": test_workspace_guid})
+        put_data = {
+            "title" : "wont_work"
+        }
+        put_response = self.client.put(put_url, put_data, format="json")
+        self.assertEqual(put_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+    def test_delete_workspace(self):
+        
+        ###################################################
+        # POSTing test Workspace
+        ###################################################
+        
+        workspace_post_url = reverse("v1.0:workspace_list")
+        test_title = "bleh_title"
+        data = {"title": test_title}
+        post_response = self.client.post(workspace_post_url, data, format="json")
+        post_response_dict = json.loads(post_response.content.decode())
+        to_delete_workspace_guid = post_response_dict.get("id", "")
+        
+        ###################################################
+        # POSTing new Renkan into test Workspace
+        ###################################################
+        
+        renkan_post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": to_delete_workspace_guid})
+        renkan_test_title = "bleh_title"
+        renkan_test_data = {"title": renkan_test_title}
+        post_response = self.client.post(renkan_post_url, renkan_test_data, format="json")
+        to_delete_renkan_guid = json.loads(post_response.content.decode()).get("id", "")
+        
+        ###################################################
+        # Try to DELETE test Workspace (should fail, it still contains a Renkan)
+        ###################################################
+        
+        delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
+        
+        ###################################################
+        # DELETE posted Renkan
+        ###################################################
+        
+        delete_renkan_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid":to_delete_renkan_guid})
+        delete_renkan_response = self.client.delete(delete_renkan_url, format="json")
+        self.assertEqual(delete_renkan_response.status_code, status.HTTP_204_NO_CONTENT)
+        
+        ###################################################
+        # try to DELETE with unauthorized user 
+        ###################################################
+        
+        _ = self.client.login(username="blip", password="blip")
+        delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_403_FORBIDDEN)
+        
+        # Restoring rightful user
+        _ = self.client.login(username="blop", password="blop")
+        
+        ###################################################
+        # DELETE test Workspace 
+        ###################################################
+        
+        delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid":to_delete_workspace_guid})
+        delete_response = self.client.delete(delete_url, {}, format="json")
+        self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
+        
+        ###################################################
+        # Try to DELETE workspace with wrong guid
+        ###################################################
+        
+        delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "bad-workspace-guid"})
+        delete_response = self.client.delete(delete_url, format=json)
+        self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
+        
\ No newline at end of file
--- a/server/python/django2/renkanmanager/urls.py	Tue Feb 02 16:21:20 2016 +0100
+++ b/server/python/django2/renkanmanager/urls.py	Mon Feb 22 10:41:11 2016 +0100
@@ -14,15 +14,7 @@
     2. Add a URL to urlpatterns:  url(r'^blog/', include(blog_urls))
 """
 from django.conf.urls import include, url
-from django.contrib import admin
-from renkanmanager.api.views import RenkanList, RenkanDetail, WorkspaceList, WorkspaceDetail, RevisionList, RevisionDetail
 
 urlpatterns = [
-    url(r'^api/renkans/$', RenkanList.as_view(), name='renkan_list'),
-    url(r'^api/workspaces/(?P<workspace_guid>[\w-]+)/renkans/$', RenkanList.as_view(), name='renkan_list_workspace'),
-    url(r'^api/renkans/(?P<renkan_guid>[\w-]+)/$', RenkanDetail.as_view(), name='renkan_detail'),
-    url(r'^api/workspaces/$', WorkspaceList.as_view(), name='workspace_list'),
-    url(r'^api/workspaces/(?P<workspace_guid>[\w-]+)/$', WorkspaceDetail.as_view(), name='workspace_detail'),
-    url(r'^api/renkans/(?P<renkan_guid>[\w-]+)/revisions/$', RevisionList.as_view(), name='revision_list'),
-    url(r'^api/renkans/(?P<renkan_guid>[\w-]+)/revisions/(?P<revision_guid>[\w-]+)/$', RevisionDetail.as_view(), name='revision_detail')
-]
+    url(r'^v1.0/', include('renkanmanager.versioning.v1_0_urls', namespace='v1.0')),
+]
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/versioning/v1_0_urls.py	Mon Feb 22 10:41:11 2016 +0100
@@ -0,0 +1,13 @@
+from django.conf.urls import include, url
+from django.contrib import admin
+from renkanmanager.api.views import RenkanList, RenkanDetail, WorkspaceList, WorkspaceDetail, RevisionList, RevisionDetail
+
+urlpatterns = [
+    url(r'^renkans/$', RenkanList.as_view(), name='renkan_list'),
+    url(r'^workspaces/(?P<workspace_guid>[\w-]+)/renkans/$', RenkanList.as_view(), name='renkan_list_workspace'),
+    url(r'^renkans/(?P<renkan_guid>[\w-]+)/$', RenkanDetail.as_view(), name='renkan_detail'),
+    url(r'^workspaces/$', WorkspaceList.as_view(), name='workspace_list'),
+    url(r'^workspaces/(?P<workspace_guid>[\w-]+)/$', WorkspaceDetail.as_view(), name='workspace_detail'),
+    url(r'^renkans/(?P<renkan_guid>[\w-]+)/revisions/$', RevisionList.as_view(), name='revision_list'),
+    url(r'^renkans/(?P<renkan_guid>[\w-]+)/revisions/(?P<revision_guid>[\w-]+)/$', RevisionDetail.as_view(), name='revision_detail')
+]
\ No newline at end of file
UTfo7S彗SqO~'%DפV k.^4{.,8ʱZߑ~]~(X̮{Xs'Zd%cpu|dnSҎ'px3:85ɡiHdecOB5'E[b<=DuGkW[ O{MKljG[#Yb\lx0y0!DkU19JJ=M|\ph|4,"%hzvתsnpm-4n6):lGvm2qx?"v00-Ҿ58tO[0/MGf49j3[4a ĒF)*/*Z{967Vg IA3|Vؐ}M_B s`㒌Ft si$ͧdsmes^/3Ѷ.'œ` } &ZhG5$RXtw{an)q^}g`ٖډ79õ,Fr^lכm3`uH`!~" ǻ/6vj>u[qzzTv1Qp$ =Me330n?ԋknY"$UQ!F.Dy 5x,~4V 1>< ?" U 9vĩ@-5>8:DvI݂6!}k#Y@. fKR ¼0@=j-BҲ+Uމl93ZS&]Z}^1O(-uQҿR6wf(ʸ!{j@FFt>ϽDR,©[N5E: r3d>RB۲vfKl ]sfxLY̏`\L%ś5иN`|sQ+_DuC1^YQyŜȡQd~#PN.<g)g&Z[7aNn8_g/OI?D0{g6{ V/viN\Ug_iDij9^ _,~u}CR" 檉T!ZE9L>@u;T#t;up^ZWj/>,vggDHM_m}5~^6ЉZ{IK" c A,.isF(E]`6h*R]^6f5rBĶ=(8ezne}iY3[? StÔ2CE$Z#!2;l-+=iEwJR`mCaJ@@ "aಇ&N!7%Xka"S`AF c V̜d5e>ia&O_ >N3%T\"ъ(UAK5s rRE'mTjU9GͅUF,Y}q V}acaV$CDz֚Ed|8ʭۖc_DHX+Z·GCm [㦝 DTmGĝjDk4ﺅ#nڊ5^I-w -(.'cIb3H~_\%_toGNJF'C:BwϊjWvް`V1ViXa$[ ^Y-V`Zچ\Ѻ E|!zgp9.Lb,ߧ_6Z~O!o%nƷ :{R u=)5n`D f2ss~ÚZ=:R]3T=Dc'Vр_GKC<"pL܃WN(5K{e:|t>rb؃i<^Dey;68>brjUEN+莟Blm"̮jIwQE2TYNZ,t\ƍCD.l7$_8c/P@t++6 {^bM>aK T嵙y_^b_,>bxBT_D=D~1lȃZN"ZM!^FhY1wS^'t NV5#$RV\TJOn sLt83娢=Sȹ[<i[[6( k%r* t/V&ͥEU".#C I#ϼ/F,j_wt1>l vㅌ`W.0Uh?ZkT޼XvD>/Q0tS٘.|o;`fqdSG.j<.!|{$t9M۽7Q29@j0#\01@M4,gR *IۤlxI?G7_ 2cQ :BUzhQS3Ez)0?+n ? +D_2Pϫ˚Ø4,ڛD%n_pag~'Z7Ҭ^y-C+g7=I4CԼQӑe, 3Yr+|=ץ[1·@#$NqQ&U;pO jZy AkpxqXvҎ,q3OVBL83lh)S,C^ orzo'NPp=Fی{.NlzWi6X]{-t3Ch١ J!BUo6Wۼu09Y(R )OGrks28lr 4.9LɏY)O:9܄EKLۭW*Mӟ&]=%){j2-L[cd(tb7[e $0cw)ݍRiJ_ )QpFͭCUU:Z9"4ɇZޟEg:n$b̞ba /2X<"(kn2P%# Z Mu bgxzD{~)!u͋eԀwx6,%e%"P=mYW-᩷S8g E%!Cخxm9H[_B oA(!'\8*a҇aGni/,9VԂB)NߐA,RAzRv9?b56nى /}9uR^Q@3f4cړɣ:o ɲI\x?FbIcFR  J)זɈ;!+R6K@ni^Ӝ*FK@$>$|In#6 +S֚%oŜ咴ܥiC*&kQ[<*N\': 6Ԋ[9\p7 8.; &3r3t;7FZnګ:#YbS}<)3v:W4$䡓UQXi@N_t)[+}y FPvLPRK#U/>vD*|"9cY^|7 &Fm otv ~џDPqn;ưVxL:w:J4۵5n`*a}U10xJؓn/A7˷[5ɥޫfQ$Wk͠~5jk\-$xx2 QgʐwF>OP¿3RXokZ qf^ wVLl=ppKs>} x='3l)AWN`'0)!T v|qw .0rAbjsHEKSprTd!V>-uHNUk~FIq= Plq{i恈s"q6f-ɡ/bi]3.1s,s퍾  ?lM5.aqhNLxz"N5=wJi/73SNW[T`+ըS':hˑ 7JcLSu%|x(t?)4aϧnn&ڤ2;emi Y`H6 @Fuc{#ښӋeK 2 OE읥(,V Y! NL!%6Ew-$߰~r@:Z4+0CYr .4j^wWite =BhBN"HWa#Cڊ<["fpXN+ؾc`nCϸa)bK1V/\gq>J%E!W"&$\9j(&.9u#>_:KFsi9*6n瀬iw/CvU֝$SE}LEvX"5]"} P~s!"P㖊։¨6! Zglcg*A}_]ʵt%$-WqjoZS^^5h<^Mf-D/ *}-nehhݻQfI1LNNljYV%OaҾA=$S%Nz+LF'#gCY79noY?Z!=jÞ<[V6ySGuSB6vE2{r<fO*?~ڛ@èOEKQ:-Cwk#j:"^q`A}v<7aUPo1aBha2ˎ41yP@C_ip$Cɥ}C.a@D}9Vx *&b3SIg`l .;f j_'cLnDXo,}.2 HXGi Eo@b9$Yy S9U;E.G\uL 5pXDj<]UesU 1Xr@ɦr*% QJ:VUk뇁}T+1r}Ȇق"x|UaISfKU>DETqPw @NQPhD9'?D+ ^l'L9,'ߖ9w2]U}ibCk9 *|.^Qg;x7g{^jXrr:RXi2q4MåO0財fbͱsJL8+CAdEA(9&H%.{qC!KB7QYYH "k[5񮜢Ut:vl@Yo ٍ_ ejf5qlgZx&Fe^<|[:ʩDPXP)zDUeĔVu$LL+3.VH1_޺gādXbMA&}֏tQ,㡃4WP٢(,v$5UеkF>1xx2/hk J D~[loS)fhq_P¶q34(}d \,B ]#Iݢiڽ')^M|teh#\gDhN$~fpeBRCUТʹh0\M!SG:bS9N6 "x n3/SXo |vd =jUoܩ 9w`Mp(E[ ACɣߧ*]66e9P*ψ$=~zSYRۍmX]0<;ۨhV[dD4(ZUnTҫЮ2_vE0vv*0Uj'y(yG/ ϱM)ՂL{P4 JXx pw9w܋PΈ{> ,NJvYT߀>p涨)GbXYi*;{0y c::BDyģ-~STxjV??rNʠM4 LEՂXz1|f_?O9'8mJP[0e?ťAHcyW>R20&Ni@ D s!]FaFL+~m .` ˖FHP]Xd_ӏ?X!!v2 gS7V(:f.c41F3V1AFj >-"JA -ñ͢RZ,:m08ҰĽ[!"_Ao2|t{A<g;u(ڽ`B $BY\Ĕy#%$lg(]'" |WIy@H2 |Sh~ mnBLHXkfZ3hWrhzhOc}uY P˃
2/C(XM_A܎alb QJ2Hf<;IXLߡ:oZ̈;'|hljq]m*GުhKLnf:s膑g[RxR+3KƖ,h4M ;=>(vpu,T$C&=2j[~_|<3( isMzٙVIN]UIr';hYOSr\ I]?}:Y{iWj &FN5Q yS|T9!2,1DS tEF0)H/\3X%Br}3.ґRB(w`C[D ~>L oaT1]@?ؕ2FXLjbLiP%2xŵllS T $TУ1$5v:.lXފѼ4#-}hv Plhqo?œ|U~v=Yo]uz7mA\DЏ L^-,8A,L*fP-B:_<>O􉌜e0Yϧ6n8dᛊ: .i@%h/"3 DW$|\\ f>jǗpKIA$ #APp+ZHVLbri0{a~b^**8-'?kS$5Q Mni[Ìh}v^qgDmD`RTWԂeq&iQJ`#ULB?]J}o@d?ftUuk|Iݱ@?D[nʌ\l ک2bA׆5 `xohF$Ԗ.px']"&c$yQQTGLY z2bE7GK<'^P& hCqPt߷%O5ͅpn34Dun@cfg#.L6g=3,FdnȪ<CSWQ &+4XnUGg)SayJS`p?3bY2.n~6r[|,X`39^$'S͖t ͷTD(b~ngPs3qˢYFB\ɶҳhDYzcqk >ر:JBO?xltsSTlF2qs0\bL@Ԙd^4 ·_5,4uTEhT,Vg`Iv8\[*1 «gM6}B Y|tO݀M40K ]z\qH( 5#` I1g;IYI$ɧDKԐ,AW E;σgj`}K =s|xb)]^ #N= [wm8!1X6Xozƭέrj oQv*P"=0i; +'D <#@=uպv4]`asY Ur%#ܦ[1مFk%vW*U@qPr(f0m~2dFiywU4dMR?45A⫡y\0#^{h1yli PLGưD:(8:_;&P, R*YryerȭW۝F2WȎϟHNwK fB~5 {T:[CqpEFca߼Qq\5rs):]iWrp/scAkbՉcK-f%€' ,(jP+EȨòB+A"+-#M\=61J; ̵FAqԷjӶ%s5d!:hX#Ц_)*wU$`]q<TFkBhK+%qxjDN`U+ lex H ci8Yw Fv!v2.muTsy"h_piCB6s,\ԅrThBX"r|+JYP3maKh-Z„ zk_~h6$A3ƨ-z!X0A }V?#9bؚˌG4xvazekY6aP,R).fͣɟ;#F-aRΈv{$2RPnW,V!@< 8)L5".\V3iwzRZ3g}C'qtٍ|z$0eg ),Ig] _qfsEORrb}}B}{)N7>Z-1E`h@fV)xYZil"Chi Wio%ɄfԿΠv7mDBUY OI˶,_433}#yxٻV``,nȤ(YKbf;X+CgV:' :¸ܢT\vL,6YC覔3/ D\ItcNKX6z\%2у's~T[jƑqgBOт,`Zf>e qk /:PAl^5c,u %5c¯9[x~-1AЄ잏pvWGI;0H(=\JxtD8 0k@YAz96N [@k+Ib[F 67N E@A{2TU!bϒex:9UaM,7v*딸{ofܓ)"< =&||ZPL>HE(9I<=.|G W jVvHO':af}x@WЦr7x'-F&JnTnV2YM4;&Bh|uKcD%~b`AȦoHO[ exSs2OWIW6^۪+?zġrqWmÛN _8s!csS[U{#滉N =( !@%~[0< g~%3kŔVL4،Xqx{(L:ypmI#b-η{&C(Kl=:. $i !kxR2z:x}]"Qcs; XI4M.|X3R0r7Myz} Tk&x$Ԫ+ִ( :JIpZKm4^X:Xm7,^Z8G*\4+C˚:m UEM䵂cT.{ '\5$N5}'ڳ{<$럎%c};pimI;'9hgN㐃u,B)TMQFt?j[5n=gH$>@ AEԾLтFJGAn}'oCd*fEA A;Dӈ{ط)eඕr&rk:@)Tƅ2ƺ"d(K0 .*wpӧGP\Sh$RQA| }"rPVJ9pIr41N1IUpI/b17;1`F -L9~##!wx5y3KkIZDRf1̰ _I}{0SVZw jTv0Q4K͟qZ+Na߶f>۠dshBg}ջVUp,|_hB +jһFZɤ|@-\y+;n}R%IߥЃr̸!$62F'ǒOOw9\^RQw`ڬk|gp;}:SCh$ ?sw}f4{O=;Y(0#woVB ,UnqYj}b 0 qL [5/nD0,s z΁L4ꆌ&9H!h;-R.M-Ȁ846.WN`cU7[A-[ޛoсdih2 *;ܾۚO\a墴P 4c6Y|U{%IpPv+Ŧl!zL#9-v3JɧJMQk; gl[B[)"J@_QT~#*CBk 7Cc-sMSℱH\;2 G] 4F-D6!,’#eRls$χ+-aq;9 D‚h!J 6S.cR^#g) rt$xyǙErTiRJoŠ)@ƗEjFv+`,. 2H"׶6=Sþ!tT?vP $ ߉TQlvHS!wBpc"i! jҒu{/5-`X7oV^79wp{VZa_VC9 QC0,˟ 3> ɔ[H`3/!G]BB%tm_5R4^"BTAJCQ@P!ѿIڄ0_ |)0h,n,xjPL/1a I'GܬܟT:;gApYzʝJ~}NQuNkQSwuG mkBEw3dC,u^Mt">L kw};Aݙ~3yuֱp&"RD紕@87{{^B-4Ѕ "{ t_&]`%\Tr t-/[rͿK/R586qHD?e y`~S!1xTxIҦ@ jO Z=Y3SτH@,!" XU7#딾xE1PMu|;@*8귮#zP]UI?3PӅ4+xu,*EQ+Ɂ#}:-Ј2z.4rBU5΍ys̟l, Npi֖Gas vKdp6,oHfi)?҉;WQ~: THEV ʘ,9n M0{hrZ8}V1Y\, FPӐWhL>~7q0GٔP]>"6[G"@、P, 8]rJEAZL#w+ &rպ$HK2xD$M zh>$ gJP4;Ԏe EH[J s%-J֊ lhD_W_9) Ra;$HuFwssڣTކ_$'ʳ%sebhV!'9lE$VLe0PҁAE=23ਬCB"MmJ`CaZv@v8|2Yrl ܘ: =4\mvUu]X0`AHRQlnȥ6cGՔ+,uOC2"Oqq"}V&mnlTDRvSOKqЦ(?-&tA:t\@_*Z'w&njF1D8‰|`Y]wl]Gel-~CX}ʄौPVaU;V`DgJ>g pLd&WS8K]<>Sn(W՚VZqrx ehEkOr> . W3Ȋkt%ar&R_Yu?bTF4RUn+ФdTGq/x{8hyK>i0!k˜9qg;/cqe}+X]"]ZxV5{`cǭ/ &S͖Bյi{k_kT~@ݜe_T|=u^`|?[vH`{JV9~%H)uǘfDٿі :"!}7TI9pX=~H w rĝMLT^N< dalON>yzɏ,mgtIp'"g.eF5 ]pD[.!klh/| ޳S,sAy<,Q~j,]Qpg4yjv.!k[3"~'<:2܌[{'䇁Cyt&-L2OQ8tV\Aay^UV3,ɛT1SmVDb'pNOi3 #dtPIihIfq ]\eO 8: ]e&9}^w=Ԩͱvi7,!7Q }HLp,Qip pHwS\&ߒƴ!]l>AJ{CnUr⬶ |ES` ;or!􎙼cAudC? Iz{74ڽ*=T<pX;ުq >E%F^~4gMTb S\7”8 5PǮ#_4ccHStj>|N yb ц+yl(W:aU[$t5Qޘm .le PGj*r4>d,)4P06M+Mi%,'l#=(v fƅ&RyF2:'Dp0=&| nA,GjdB-P.Ymq~}^DǢꆴ歑vR` B. 0&lْ$ PZtoצ(Q9+͠,ХOan7I{$鈷|ju6RΡS224D~aU2FyZY"ТYp(-b^LaAAޣdVqfJq )/Hj"pQ00:_;F`f]e`ʠj>2 xY.cJaCuQh&T0ߏnNPhDF:6w5F<q;^^1-R@i'jOÐ=ˡij}l<}u wg0%Q0ӴydxZtD3yWxc%$J cʖE鵹twѼ˖z&=,vG\n 4AQb]Dv&X9l=u R^ʵ'hSVz ";$cOdcw%HƆ `C %iK+Dzu0!(It5u]Λ&-ʾCkƢuP(JG[aA!'Cֱ?ZA"ʳ! 9]R'؍DOL'r ,'%kA{u>F+@,gaJ򋞦| n*r=>$XN٦ٝNgPgz\M#PeCQu^ERjeC!`Yk o e$dvQ 43z}͟k$b &e]-,:r(Y}}pT^Z010u`f]\pCo7R%D%rc|dO olŐe[z dUw-GDypmMn%\Kl( >ekd' ,W|Nv@lh%],9 3\H|Q*؎xm$(hl}.83󍰃Cڗjke2?ށԟ%Q&~O+=|; 'y$Z:??@:uԏ=Q׻A^H?< /aZʜh[ۼ8'Xf"u6#"V*̖/rԿ "y< ESV'=+,PoG(4]@dtr/ݽf4%p Cv@lfV ۔!B&B.,f-6urpZraAQcϬ“R㷲ǞK R'4u=5iFj1%D e9ekkAHn EJCU.sRPȸV[d( C2^H.cFm!1Ge@O*<֖׷u>Wך;æ1 Wh[-zӍ0.6Ynj OݸXni̗rqb\xYv2Lq@,[/z}L&qXmai)ɨ$pN_ʑy}FA0<Lj!u?@M>'vsaVEB5c*XgL*A]g/Uj"e.9@$8) T67+*%v2]u57L*IT7pckc8jO'ݜYf8SCsl1(b#YētBQ7 3G Cy]@%3u_qhXLckӬN&J't~ZnkPpqhB?2#&ZWzyltg*wWyՕ`6gMIF1; wo/SgZvLK JR! ^OvUWNWZIEVm?kwIG.XO-=G{/8ˑNˊo$B'/ɔ_t0?X83g%:Nخ QhMoWA렭0pa Pb.zLa `%?&$?1f(5%rQ&uU߰XPE',25eұTEyb#G{ eZ! Y܊uL(72e*ˆ?zy//(MJ &?ShH bѸҩWk586~]v3lE|[\޾ԒȄ^Ƶ>/3Mb`/;wJ,,za?W)sU]5B ^^@0K+Z܇Bx&UAM0?ENSOyvdBuھ^OtynZ4Crb1K5TTҁH.ʇ&)sA3¼$ŐrIRQőۆ ~Q٘Z~UeNHov:G_y6H3D°zL6:s>綰+94ND ёz&9Q'ڽxцz88Pe{E%$0^V#) BƯZ:zY}ƓMz[;pk\ IgeK T!C\"yǯEpsؖc_#gFe'6T@T~QC6YE˘ymK'^ڰґYhia%|sY-[$ãeZ?sjF9kd}=mגcW[IY8;+\%p}" en?i^]u)aA4~UD \`CO'T׊%Hʢh_bExFk !,ί 7_yքi PC ؿ!NMuYi.wE6 UQ)3ƕDJUC$g$Oh?ٳ P2xQ!۫ցRQM 8Y_̇o+YLm&RZYqKt!]ʳbL@Tol$o㻑g}H<4ē#m[r˜C]~LAŗq!&Vwb!;fn`"WJtOT :` aPԗgQ;?j "ãOɌ.^Cl͢}+ ŧŘg||)MBsQluJҙOAWP}fsl1<̤:>uXy8+stBTX.nOJNo@0@ &Ph{UNHXG,Idދ?۬Ӆ}{$2mFus$O aI Egb^WA$K28Ƹn)v+h#_,䛚#55~R(".#l/$#`qWE9JP1NGYv,†?|b$[sdsEvaFI)U*Sz9= 2nH6ssƕcNA}ӵ0doNݴS"- 7fjv!RN&'s,;RXSN;~9^qy^tR(Gf>0ye̛j ޯ/5 ay%OF?)KWR a8d hlH,d X Y<գ$43`X^1x:{x:BBhxJ<+fd0>q79b@] U.Įʪ_qrm$Z:jH[t[/(iB= ŨĔ c"CxCCbK4f'lП)1EMZ JI;q_i+qN@۠ںEYZFfQb ]R>eXC2KxZ0ýA|8v=w߸p,CIvY4Oy˰+l3 Ǚ|1QK/)Sm8˺3S<rT~HVϤ{-|UcK#@fQ~jฑ )3*/Qϑ-[+\a A8}vIQ TK{Ȼn'+3E 2 \Ǝq3QQ^Ij5L`nʟMt4aH Җ3: LvX%=$FB`wb'yGimJo#> o@΂\? W1 le{d),AJ*ؾv3|`P׋i^W9Q[Ksa;!L <^3k=_ڔ-]J^f@K:q2ii6sM3 :aoT!JcO4j_дQ&4qey%DAכ[y绬PU ooۿ?Gב[?.͍xR[KixN~}V;ؘw&^uwBPh'Bcʜ݋,z"#i} j?MIUJ^?sjw ,PH_P 6g[V%40|5@|O29m"EӦm*ќX$ZaMOUݿ{a?)x8Dhǂd!SntHFk'J .n%{w 7d\`ȌNnX0KBwyV]W> Nz:Plp&9q머isPg1BTߧ0n !Cw!oN F ]w9C!*{7рiZ+>:Mh &B`O(d{,ܻw'/|@J%bsH7_WMk\t1'nFr:n㖌zt Epi"qtuohsvɚΜ9~|Qf.Q w`T+chJ _@'q3 ?@0E}F}a7dsݎJ6P[U!FS_C/e:Abick"Y#g4Ktl/ 㡦 t-]@Un?uDl㜩[|E؟롒1a8WN.tBuXJgL&;xzvs z9dG篍nU2.9r %с(r=n3撒J|_J=:4I P )g2mdUfݱՃim=^3vFIΣJqf'kca]2J 9Pk{rI=H7„Z]!6q6 b_6g6" ' 2:a}MJգD`YSL Bb0b 4 ӽ7P,|byJkWb~:>25t}"DRze N|p+ DV+ϫj+(`PyksaU"B++MO/ Pq鼷\EPĀz2k OVřROiyvMR7;zi#&-xmHU}N2ئ6D 8~;Hnipxvn& D{t$jٯX_X``+yaHt!iy2 0:'JzDIݾ0B8aߑ~WȻO%t`3֮%u]8GNBH|Ѹ֥Fٹ)yy)?k,prM/^zEq')P2 )-r8ZݵR>$(/ !Ht>'$:圵uu'}1. ‚hMBY1V˜S=% DX;8?+=ny (n(bΕ~-Hh6xj7 E~ZA֗N O)Aca(P,T'1JX.m0ޏF-`8[$s`P=bhz&.dISA ʉ˅%>^-/"LQö*Cr fIR%,^CQc׃YW1JdPDۅ>P[Բ tKDDIj+d-<*'e8oV '5Ga 's!L/<ښ{/L=*DJ7u&o' \ ]˜A:sdlS= ILPxH .&\[W4(Eu>@guě%J7߿ Ә@܉ eõPXlY; ![2ƳPnij:ٿH@>(!?6H6ZO׮U9-تa67Kl1 C<ܾ(\,]w`JRET`#.'-Q,W$ :B9|e~7 QqLPV٬|h9LڭJ[S:X2jqi!yv61G.{Q9< TzZ/n'GEV|BxI 47b?܈p?~P/ՐJH`Yۗ19I"מd<, FVv|`&?\sr.u]yx׎#@y-4V!꾀 KM:z%^m? fx؟L )m2 >b7HhBԢY O(O{k!L KJa΋ 9f#Bɑ`EXw!*LʞB;'rxdiP%;+VM&={/+'%hId.ČᝨfG]j̈ú5g֯@&l;˧8'5#oYcV`kZ[%(m7~Q+:Vn]IZ*af먴 S¶Q 3!)$JO3,t\e„o wJg0r{ (R:-VT>\璋z; Ә QrzT1͟9z6Žf2n+84tMêز\/ָm 8;ɨ]f/ZF:6VzG)eNZ [1sK Mp:Vppe#Ee)qm&BlpF/Ģ@zB K0Lj_#{P?$IOVaKEv[Ig`h {lM b5s^i ?lqtM䚪GțI 2'pRq19u܆鎳DSWTǏ3pC9 ΍Zj^nc3@/X{ D.9ۿ{m#-3چ8jR['G@w{׉VkO+pS+M'I[:֎6*9дAZ@uȗFj}ig=řT[dDļej/Δ PB6ʯj#%ՈއFCzƹ)Rj"-.2K$qCz%="rDl֣DY풫 K1HٳK 4YCE=)/C#moKJR׷S?p[À-P;xV힀MMMAnҵ<4L{O]BO{.q+dɡ.*ʦ`3 ͆eܖ|_0QeSdPvKVNuɱ%c:7s!5]IҦ2w|O `ܭK 8@2E@DG\n6: P"GeueG+]QkLVB u e綟HiRA n1ȭ4\^-GK\\^qu5|+Lwj!至fF~._O7鰢zߞaE&)Ր:} s ~vH1ْ#dHf|/a4g񮐺EIw\Tp plfb ^ O}[1ށls).|DQ3}g8G~}ƮAAs0&ظ|?! p2tAdž"\8#|#%Gc}.1mF P%i-@sdVL=(_еܑjw-Hޏ"2?~'ss2oVA Bl}[DHQəkz;ă4f&løauʲ1Gx%6*=64GYd6T-4 P"g*&`5}Ba60oFr"DhpPEgyPXPC7Co'iME[g; W!'tSPHkW\ǀ9CuOy¢w"eI-iTbBYKa d!j<Í ſ]łچfAG.\iخl*mU5.#M-&!tpWw}ֆ5q8ɥWC;jJݬHܢv|Cq:.H"46ad R-ѓA[?^02 g-@%)5B 0Fnd#KJwɄhOŞ˔ypʻznu6~b%[Y<`ޱ ll.>iXokkbB!20[ka9*{RXCUpJ?Qc= Optڥz)" )ʝ)68ǹe! 8m 5<-S25f(h?X:bs5fd'|GO#4wn$>W'9MbM=;Ը D> L>tAXBDbBv@BqvU@92^yW%@Yr8QðfʜR]Z P:| beV ?V8YXV(a SK1ĭMN;]!/ SD4e1+a čAy+SA-mN@y\}Q}x׭ d?`b+o;\O  x<'1!zV4Th-Be]#D<)=fk#6kI.ϰk{v#+;8pJ0Rf\u%q$ y-4?pI#0OM-?IO&F`?w $$*)Ag ZN 0:{l[7t*N3GHm2!/ 1G+ S"닅(̧(DI|:q9#[c W2{12}NjɱJlNRMq2N,n|*| ${ Hڮb*T-ą_*A35 u=σ˒2W%z 3bg]TW( . |^0Gm_7jq41%E['ԡЫNN9Aw] k'gR*@ݒѧ%#>ݿ^FgxORac(,(y7ᐦSEvXWzx v&HsTW 'e:Fm׀ ) t,+=Z7),4nQV:&vU|*E@c6˜Nx3-I O}ffjz{T(!f B3 {% ѿ7y+ =E< .ObyEFYL!łg"-ϗqHleeS|2DOQdn.֖C(cH|T%%w3s^mWOFÒ Y |u`^;3O83u^5jzۥt)ۊ981gp{EH+dMEŷLi'xZR45A 0< E>r/fRds)"( 7C:Ud;-9j/ph4?*DTh@aGqF!mNOuϣL}L15r@@9JL?$2)L=b9ؚّ.1j-+nm>P{Yh)IlHdKL L"& cVF2).{1㡵Zܳast&:(1p0ĸͿ)B}d#x7 l"&kH璆 &Yz]ए=#N5fm˷/s`<,fdZWt]vk*$ɏX%ǸNWTCfLE(=¢cUJBY"c˚pάg<參Cp!Y~mR<ʖ*j"Y"kU+ɋG ;s tj\%m|E¨'=7ݽ.d7 dWl?EJ<B2S Ε]5暼)sԩi}`<m`AeSD4f./֣Z-FwGpvy'x<#9Eqc @( DfMq  w4B RXUs(D7ö~iymZO}|a1hHe0Z=HlDy)V=E^;x%Z$z|m`X=Ȍ tb%kMDzߐEp rr,9c+E)Upt$O"9]z7~k!\T$VtNy5ͨ6DHpk;QW貶;_xI8g03UUdd[7q9b9L0D2Y#/7^ ,矲w~cH^6`X_͟t=)ԗ0X)tbXKb L oD^bCթa40,(FZD/53'.lk#HcDJ1-VIi$:VkKUi xi08 08vx}kFD3F:mf(UIqĔB`OFG^Pѽta /;UM3³4a.j xv 8YW}zM5MT*%rKO~%n*N5՗a #|ϔ5]IskDԄ`Tf .ds]YCIGF04t-$*xEqAdeEhΜx:r 2M ^t\h{Ey€=Jw후ҟFp̎ &|$AA ث2(9900 k &P1=NS՜LUP%O&p[M?L#XhXf0#1r C dϦ3( q 8mnyT\HMt烿5/*<{x?HGGǞZS "Y 8/+TMڢ1 n Rp (j! (Ʋrɠ]Y̸)p*ah|2RdTa)XAe@kj)P7C OU¥"F5WyMFbSR" 4 V7,rRc3@Moܵ, #`m[&cWԢ7JrLI7kXa_E&uҝ/mK"܈`Qmߧ0&TBfPbXlv7u ;k 5_fhIM&ae7/k 6<^|N*t^] r^ ATS x5\|AF&v5Tޖ^|X bu!0JBIvFJȱpc޲[~%ė4.hc\˕5EL@4EƱrM݁#n!$?^j%ҧ;4 :j`u4 ɼY< Q(SLU\}k[t=XT.~S8a9}Dyk:.LJJM %wCA|`jEiw& -փ[=c-K^"ye/8$B㫕Y>R6_|6kG57/+[dBxq` T22XHE}I`$$›eJvQ=%H4,R60I*D>@لY.4[[>wR܈RMGx6EPXe?JIB$FyAmf/cd[6{s}o2еQΔY͆Kkir*F(ĦU4~Gb)8gX iZ/%&Tx*Fԯqѡ&NU19!cE5<%P U'3Qħ P9EDX),OH+1xwEXeƐK",=׬JCmaH,UO?O,cPSzh"=c<j"֥{nFo[eܮB HC⊽/1 _vشeLc CE\IA#*'6Fe)]̝5Jgz@Qdxb.צ%˻pcB أSTrJQ(̷0Z3܃xN#dPS})8s*OFǭrz[o`,蟐գ\2P{{>f7D4d%NrF_I=Z=%2u튆oh =Mۇ!O)LYV~vu[l-F_@hN pn'!kkTU~W,j5{Ǹ[S)Mg9:qĸϓ$/OQJ-hQ |Q*>v Gz!Bk$7 NdWջ9b߭sKod1olF4ae9!RRnE_uW 1qg i| DVIa6]X hq1WuM4 f5ը)2W>'12! @3Ee p 7I#ˆ{QCK`Ԯۋ0{ MCxSzet9}jBv2P83}>*2]IdT冈4<2K_MCMv-k䩇u@? }z"=YeGcl'[ i:_ $ <_dͨb %171\SE-* n:C_(z-ckCLpCKzQ۝<"[ {У)Y` ҭ0Eb@xDA ATfC$ T:ם\*>: Gri8(HPvP5HiEW\H12mqr$C3U˔y D#+X"&(qp!"3q*rIYD ڨ#m.;gyeQR@Y (+K'`  Wh$l"l & 7H]$f*NdR!ȏF9rc 2<1h8!]5‛2SB9[#7%aSXokv4,yVMH" bAH9`(p@H׈XorI3. Ȍ<~ .H ޺ӃAPU@>i%}8떧hO y?$9ƱQL9tL 5U"AN C2zUK{[!LY^PU.B #FK#N[gAb/B fVVYA5ժ*h:z##I{  (+,euH=O>W~f? %"vaC V;~x{Ђaqv@C11푓CcFHE=ZnRM@ncWwOAwe+YNH顾NH&Gc."D-`fpC?L: ޔ aB `}:aw+l& 82|m_P'&z/ h!cah)!E T Ч@=u$"7 `FF=@p i(ăRxŸ++x>^g@$z0vڤ`@],Nyph93W? "!  Emvفùk[ "=e=efDۙףB"TDI2wՑ[D%cIK,6'儤G!,=)ĸ|OCT]xV<9f`@@xp!Lc/:+ JټdnU7K 0`\M3;&=D< 9 FW&/b¨?AREv<PE?9)QOo(L j zeg-De?KλjH0JBtt;C\N:iEB 'J$/L-R.UfeEXNڗAM ;j˽ ./>ԇU 7ƬMGng7 . +n4ٶ b;E RԘM_O^:̔yUP12,L1QY" r0nzXi iGаNU fm+E~jeBꃪuweq Z$6bΤešgFG!km>D}qc᠏Yj45e`L |1/2xचSѱ'zH aۺwmxOM)`- ۬DhvZ(Iv,-48o!Q <$' 0I!ɩ_dyrR!b_%uh1}) =mLgo=GEH? K>q?l|S**Oݼqd i鐁u4}g_dؕ0X#HXI )Ub<#&ǡ(C!=*ou@-r)Ԉʮ&][>}0,ZR~UqtRtBy.p` lgȫ8Ggab11ӲF锝Ќd7ifnua }R4S>bHXޑQ8]1g `n;o7'@ӒҐHRJ * G-zˢ TI.ү⸐ΫhChTt]tuTQIXw@pfk&Orcb9P!z{zS\v mk҄$/)i>8QaQ5ClL]N=‰BTUe& rI~8\rd(+* jagiyboc P/4ܩɋ?yͣ+D: .7V L |lR$.ҥʂ;,t _p"8d6 8T}>$PxU Mj@.q{TR$u/3 t0}8$>?= D˕u 6NZM|VbaO#P5p;knBۦk}pDX}d3Bzmq3pN4za 5߸r.rr/@]iFVB%jedfv3K^P~*Z(pb`Rk$3_5yRȘ+}avڥ4P K3q Сʡ4`P/1HiU+y; CǕQs֞xhE6Eq`@kX}1*(A 4;`#n.wRcս@zVk rC9&W8BTcqk-PJb#Hf]汔GZ̭ l75;O)U]!_H*KN8nA6lq B>:$"^i9`ϸS*Je B5w[bl%R[0OE]!y x(섊W4lad2 gJo)AdV]5.Q!T03KSD5Da 3 aPdj4([I؀JɺH.u+$a-F0*C@FvPW_(z.2(WpL=gi?'xW`YA@őCFoVoW)_ I %$-B9P!$x̲mjAguة!vUu K((4Pt_84 NįՕFp{J7K̦Gt\qE벩,ȅ8sʆ5& zbHhs\F zD uѺBMXIxh bDvт-ދkW˨ ;<81,Z+ :p:Cm$8łh6ʥr.r՜ٰT@fDCqaP|Y|AȍJHcpF6Wg3$EM/tTit.m̃]Np$LG΍vz T9B`Uui.d@4׍Z7 C`%@Q4l,4E$$Iw1+ތ{> > 2Q3vFHDR5dӹ8qs@qgjuh[. 1+=rfJo$"VĂRِ`VfHw$AE _o_{vÙC2S|tNNuIsȢtb¾rg҂W1IG} fe10ܺBbƧ\\*N+KDڌLt;+ZV.*ؔ==f`'TѠ%SW)|ErJ/ׂ]HNVCYt9f}ЗZ+8*Dn3*Rּ&O*Qc!nX8Ϋϕ5y n_ޣ4.vm45\y>9Zc(,'޳ {n<׮8!ѯMwI"ֱ(i /NQ5Y~U+*38tW[n}~C!DxαRZߏĩn+l/pϩ8c{"L@ϙXVÓ ìS6c UN?\}q&؈E.q@\ uL0Գ̎ oU9ͩms`) H?2C)`OLiMW_}Q4xޙ_D>D7!t+]<+E95}Z2#>]v[iɞ4*^9xp"e!qHE8hאkrcSEkͨqVd A 2q`uA0S;fWjdew D_D-aJfAwc,*vLp,WceFkGqxA- @( t_2T'VFx0&=+j(`E\%@hLMd|#X|tR 䀹zt/W?L:J#Das]bAq(Fషn1$x[GS]C,&U)kl{5 f' 5r 2\J#z=` XhabE9((C4r+rӵHD&wLuzD3dzM~! $AyUkYId"E%kN O ;Yu.So!aU"bq47Q'=aVd6A,Xd f3LMG,C3b;UHEJSeD`UNyv$|y9^ 8ا,ʲ(T{3p,-*qX.a{5$+>2D|^~>LQR;#ƭ%?=9gͨ &9c=NI`$cWguR ADHA&/]L1KV dmV[FnI8zpnT`8Z!5ԞоCDS1<: tZ!etF)(3|%A)Vg"wn "-NeR3δpku3Avb*԰ w0K.w'DEl.LFHMo9_p%1'`btZP#&ܯ.wņ~@RU~%A[u! &f "&9NK $Ki-7zM<&N9|p}4ay)T)L8_6miC=ƗU)ُee:ݦWO7S8 U",VrDI%pۣȞIY5ؔD,")!* y& HXڤ`${|MGa1"h0XdH(' iG}:6+Ӿ nb{8쫏x89䆞EMڴkd n:'!l=-g:X1SȪИ>lS'==tϯz][Rm6DTz'@!f`&'FPyMV$| rB1#RȽ%0[hQycIsL`qIV'&hȶzF )s?~Tu؝ݞG}rK,kOmڱNuƃ/!T!ڇl]:?brp"s@Zv@}% ̏yL"1&rg뺰gAq uݹts">Xf> [4נb9]+Zdg3Ʋf< X$xn%bD-# !R5qY yݶVGy/pPbABf/ ?49T{f(-)FޏXh-Χ$@qw:1)!R^+d\ؾ>4Do5 M,D>qQOP(+j[JsYIۤopsq) *:sw34z̼0 ]|s|~xE"8\7--k" HxTƠǞLj=AbF9M3ɢ4e.HJ;rb{@ޫ{Lp[SXfUSrl ;{;*\WN<SlpCWqm+{ք75i?ȵ* 7SbF4(y߉4VJu aٵ!:ѥ>SNLF[K{~MQÌ6Td{A}$$ĄXpLHE|gnQO(KUsjl{l%LrS 0|lk8頹#+nwاPO5'DïU}mI˩vXWfM)vm7p\CPxe TYg-b^2: @O Scp8QhU{zvl5(f (ф;L*Ut4<چɬZRM 4x (寃Y U}3!c'>SGkB1 )Kid~ ~ - ets)mVj^ u-iZo)Z2#)bAGŏD]nii>[_e(  FuJc d4IDF)?\xl -38ceb)څԵչ.'ˠU}Z<]0$i[乗q:a/(\&jCJ0"U9,y)= y%4Ӓg0+AЖ 6jR 0?<.H``'T$yn3bxװ3]6J|Hhoq ;OwDmrO|<3ofV58= GF;acevs>(sNJB:4˕۴R{]y7]ۉ~nޔ2K0(ҏk9څmQ6 yEWX+F6SYzzG=hܤ^N}i4ѥn{7A HMW)uBGx ~L ((Gk" ΅mFa wH;@ڒOO`{,OVqB3Lӳ.q.<'}I]Km.UJ;5fk9 b𹎯a%l#wgo&2"aMW)@u^osC`;OA_X v6#7qE;&l~1@:/ .w>4\P/ cS*f{vŢFY56O^[ rOxJCMY왞IHtp s^GB~2߱$mՃz1,)@pU*oz[ 0.&Yov@#=X6O.Yl}̀+t8TCH1\ L霬muDP+DHL^J<9϶7e @W-}@mr_LnϧQ&Z=LH\NV1̆'B͊ iT$!m _ ߧ$5XyFo՜UlA{!Oh"e~Ő=j* ˌ_(㾂J 6ɯ rE{b_8x7Ҟc륦c:/uv bS?7{)QuvUfc8(]7D4IQk [R3%H]Rg~}esb@*qΣŗge]y0b^]!h>ئ^ Kkj7zrtʝ`:bݫ>y%' ) ή/AB]/X֔gs=JIs3fB[|`!GfNARz"}h60sa1I;JpO}0g^G{Ѝ$4,("pP |.kx4fI\*5}xS.Q[n|{ }ǩoW'ʕFܴwж7< ! V)Ȗg_Eσxj?F/1dL"ҙS'6cʌ]rF&=ERZ97U eon枙n>AzGUKzKκ!\p ^Hkebʈ/o.ht BeP{]L@b&tCHZ zRq>[u.L"꘰<(b}'tm%/z*BdBYlI|Q|ިP`U?ǙlaTE ,cGMng1rO+b 9[> >{ɑYQr R/A,J[Gkl#.޺ݩ މ80;'8ubN2^³և]NUգQT'ߟ_;3ԃhZqS+<Kb:n~#&L"{s +egD*9C5Zx޺әs˞$Wp1lR-SM۱j-we Ex>d<s-"rOHUF9Sz[!PLnWAąEA iWELhٯй&@qC]h\Y匰~KV4vOX@^ZgN/{Fkr#@].N^Tf 8HD[g\brhj`!p/1,1UWT]aX qmG(";*=HO\5S` rYoQ0GP Ǩ%!1NNHS,z ]^.4 VFtϏ@m,,w8'\hJ}r PtЖvSkl]0e(ȿ,HG)oFYBLS_TpSEy+Ncvfj]I8#@y&qe K.c# '' Yj|0} ({Uj^8w2Kg^ vӖIYDa<]mCP3ß3}0}0Pϼ'H q޲WVdƂy}:G1 O]ʚ+l]ZKgf3ß}B"ԹRiN dT,֊=8?#f2'RY!r@jڬNv~a(ȏ zd\~Gm?UB(]yk2u֛:iG @v| Ri/;:{*Ύ,(y| u37v-Э/PߙtVhV`L*Gg}kjW)!TOw%#J=HC.xvS)wcGعOy\]4s8< %E+{]O2ޚN [x,z3P $i[!jxs*ff=-jYNtw n +Za3KqȱHoQGsuc 7ٿa?1;3 L0'KZ?zaQ )󦐵}e_^EzI'U> P4C:H*y@Z\*Mq;gǺ-`4 9@dxLw?坭5B{zߎCT_tR1$ۥ0#%?9jJpDvR,TC`7 @ iv?Z qZ/y|#}mԌ\p"x;$^hLK gNytnwlPș<1®MaSc!r9bی*KA[d Oh,^/0F/ \6?7`BE]!9^*{R̃!r1JxkCMV e5z *ފbT^de|YZQ_D bSCL8~^q'5P#UM@`N|~5i~ֻloJJb;jYKt!㜝/TFX唚pC)Բ)37X>t/C8`MS?1$6-SS2IV)GRˮJ"㆕k@^ `lӾ÷;YQBC(Vƀ緩OFj'o"I>z c?w?=H$G֘k?50jSQt2@۠ f3U{}6{`mV>;kǹo o=_wI~iZWmK8YgP.0:oɧᰱi>΋^t(,dyaYxã}XEtx9L  2=HSƛg!Z#1ݹ߇eÍSw5*P!S6F OqZL|*|dfű.gia 534?]кUl ~ vIP,ۨj$7r&v~Cg%vd옌'pY|O*,?]v+htu\\,yq %~K maNl#ӟbŀ–`xyx(Y5`%mY/EnNrj&)ԩ[*hV?9 ~W(]ddUDAO5`![Jgu#vJ1n=≺}xȵ 9yO~^SFE"]1l(Vhǽ,թ?uji0\7_J%Q#lMҦK)/]ꢛw|뷔rqۏU3ff7 'c3*(G$| FyfsY ٞ"%ד V_PL0x#{,ނ<5 0~B'jONnZa(Q<-$!Y=Yvuz/>'M؋yнM|Kbu|/!-sW(TXo._KGnќ-7Xt!3 h5rj'H74{զDHxPLfי{,ou-BqЅyCcߝBjko2x V;nHR\1(w]uR?H' J[('?jC$c{ktTf/ !`W&@ q:?c:c!Dk9 I7>(8iʜu` <aнg^15{H5hgk*x e0Ҥv'6PB]R#M//<;*j8X+ Oe#n1{M~6Sb-P mBKBx.\ &5[Tм]{bMa]cLj{K+p`0G12RY4L=Y;*CS'2POb'(-gQ`3XYO`ŁnJғUjE_,OŧMŻBqC4]zeBMG, vyvhAC˱ϐ:g=O!owpth"򣈑Nww}lhhsZ=ya. Aץ>\YIC {XWZx: dA>J[&cCNw{嶶ĚyY\.8 (gPM~亥OFD3rod#}xCB}:]UP}wQqdq*ΔK[|#J>a嚏1vq= {vx$rN"NVȸ07 r:7CJyR"4xӓ'irI)ӓATة?-(F%w͚Z_V@k=m0M^/{.^u\("ul 4B D0uSɮ]H &x+dRyalN֝&q9ک159{2-"ԝP[%Af0*S4GqNOs?;tN1 2گz!R} v8gֲ[6-l3:7u~^`q3N廱nQ}~^&j\6QV_"ME\^Ckdͽob"YfGFܴ"x~Z"FF/&.i:'&NJ=I u7NKkc 9.Riy\a)*F2UYA񦨎V]ٞg@ȡ S?sl:T+֝K\Hmle'iߒtR) Jݟo`0퍱hR_苖a|Z#W|@MɽaceJ ÚNᐚB3}7qSazLl=D*Axßת=ڤêEVm8kK|} dǨz~> ,Te BeXYl&TM Dk)%t- (rG_ ^9 {#~ptjk4d{:Qp&_y_OHU)/tZǹS)j 8iZۓ4 cS9{ 3~rYsljSLom;k[P9J-/ q$Zs'c:ZWmSF2~p ް$HeF]Wch:ʼn<0sϼ !U%>̨^6ˤ5+5q>.f~NhGn vN;r` l!՝zg [gb&**VMPա%>vri=844LmPT{rw-ƸL:_MJ^Ā0XTŒa/Hv5pʯ6x!^W^b#pD)Z5a_?3l Mf PVzOUژ3dDX4ػjvA ?&MCgm3ԑX[gl1N=`RYdvG" ~Uv%BZ:/ k,% ^3y7zp- O@[j@38=FWd΢n[]vҵc-S3a5qubg%yn .],G3хc9ICmjE;3Se{0=Ir0." !m\=|ci:vNksN5[>꼱YLqbHB 7U| K=9-S׼hMAa6~%70Ԯ; ZU^(=HV}Z!b^3Ql s{]ͧt`W~g2{n$hY-4nie:_ᢕwec]FɆ\I'hQG%r7VI| 7Bzm|}'3[>i?d Jz]4=*sq+zMSFz2A#>)p+ |e7w(m#^ 8ۗ?_\bd\kJrSi0kl%B|$ogV ie +nۏxѳmiF+M -)on#'>˕'\uW`*"ˆg)osNLdIpBmvSZbt2onRAI>(U2ο>;3# e']%(L`_ 56Ӽ{ O=\@[#rQZgX'H-Uҷ1PNޤy </8#ęe&ucǩ.54or/2W?_ ]BF׾EL@wC'X9״&`[mntՆ\ 5b-4Ł+ۿC}a\טS=_c"LiVYs^Qd) ;Z)Λq/+\P'>רV'w+`a0Q7:nnWbEOgRz+B;2]pFtji2!hЦ N+|fT3;ƯORχhAYѤ]ef$ ⃃2͡htaXz[v7QS1\!HqpQub<_-k>%kb*6eK;-KaVulũ ٫)i8Stl)Cc0AimAQ뭭LS|Ooуo[{Us^M:3'हX(j`"> Y,'NB(+=QJ8)Pd#./5M3iҸaf+XR$DMr%yg'b5Y{X&%x{w{(cqwZR4j>f@џ:Fvoeow$S 颠