work on models + auth register/login system + adapted existing app and templates so editing annotations is working as before + created empty templates to fill
authordurandn
Tue, 21 Jun 2016 14:38:09 +0200
changeset 24 6b6b183447a2
parent 23 713882b2bbb4
child 25 843863132ac9
work on models + auth register/login system + adapted existing app and templates so editing annotations is working as before + created empty templates to fill
src/iconolab/admin.py
src/iconolab/auth/urls.py
src/iconolab/auth/views.py
src/iconolab/forms/__init__.py
src/iconolab/forms/annotations.py
src/iconolab/migrations/0001_initial.py
src/iconolab/migrations/__init__.py
src/iconolab/models.py
src/iconolab/settings/__init__.py
src/iconolab/signals/handlers.py
src/iconolab/templates/iconolab/annotation.html
src/iconolab/templates/iconolab/change_annotation.html
src/iconolab/templates/iconolab/collection_home.html
src/iconolab/templates/iconolab/create_annotation.html
src/iconolab/templates/iconolab/detail_annotation.html
src/iconolab/templates/iconolab/detail_image.html
src/iconolab/templates/iconolab/edit_annotation.html
src/iconolab/templates/iconolab/home.html
src/iconolab/templates/iconolab/show_annotation.html
src/iconolab/templates/partials/header.html
src/iconolab/templates/registration/login.html
src/iconolab/templates/registration/register.html
src/iconolab/urls.py
src/iconolab/views.py
--- a/src/iconolab/admin.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/admin.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,25 +1,1 @@
-from django.contrib import admin
-
-# Register your models here.
-from iconolab.models import (Tag, Annotation, Collection, Image,
-	Comment, CommentAttachement, MetaCategory, Activity, Notification)
-
-
-class CommentAttachmentInline(admin.TabularInline):
-	model = CommentAttachement
-	extra = 1
-
-
-class CommentAdmin(admin.ModelAdmin):
-	inlines = [CommentAttachmentInline]
-
-
-#reversion
-admin.site.register(Image)
-admin.site.register(Collection)
-admin.site.register(Tag)
-admin.site.register(Annotation)
-admin.site.register(MetaCategory)
-admin.site.register(Comment, CommentAdmin)
-admin.site.register(Activity)
-admin.site.register(Notification)
\ No newline at end of file
+from django.contrib import admin
\ No newline at end of file
--- a/src/iconolab/auth/urls.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/auth/urls.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,13 +1,13 @@
 # -*- coding: utf-8 -*-
 
 from django.conf.urls import url
-from django.contrib.auth.views import login, password_change
+from django.contrib.auth.views import login, logout, password_change
 from . import views
 
 urlpatterns = [
-    url(r'^login/', login, name='login'),
-    url(r'^password/reset', password_change, name='password_reset'),
-    url(r'^register', login, name='register')
- 	#url(r'^logout/', views.logout_view, name='logout'),
+    url(r'^register/$', views.RegisterView.as_view(), name='register'),
+    url(r'^login/$', views.LoginView.as_view(), name='login'),
+    url(r'^password/reset$', password_change, name='password_reset'),
+ 	url(r'^logout/', views.LogoutView.as_view(), name='logout'),
  	#url(r'^password/reset', view)
 ]
--- a/src/iconolab/auth/views.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/auth/views.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,6 +1,14 @@
-from django.contrib.auth import authenticate, login
-from django.shortcuts import redirect, render, HttpResponse
+from django.utils.http import is_safe_url
+from django.contrib.auth import authenticate, login, logout, get_user_model
+from django.contrib.auth.forms import UserCreationForm
+from django.shortcuts import redirect, render, HttpResponseRedirect
+from django.core.urlresolvers import reverse_lazy
+from django.contrib.auth.forms import AuthenticationForm
+from django.views.generic import FormView
+from django.views.generic.base import RedirectView
+from django.views.generic.edit import CreateView
 
+User = get_user_model()
 
 def login_form(request):
 	pass
@@ -9,4 +17,51 @@
 	return HttpResponse('show show a login form')
 	
 def logout_view(request):
-	logout(request)
\ No newline at end of file
+	logout(request)
+
+
+class LoginView(FormView):
+	template_name = "registration/login.html"
+	form_class = AuthenticationForm
+	success_url = reverse_lazy('home')
+	redirect_field_name = "next"
+	
+	def get(self, request, *args, **kwargs):
+		if request.user.is_authenticated():
+			return HttpResponseRedirect(self.success_url)
+		return super(LoginView, self).get(request, *args, **kwargs)
+	
+	def form_valid(self, form):
+		login(self.request, form.get_user())
+		return super(LoginView, self).form_valid(form)
+	
+	def get_success_url(self):
+		redirect_to = self.request.GET.get(self.redirect_field_name)
+		if not is_safe_url(url=redirect_to, host=self.request.get_host()):
+			redirect_to = self.success_url
+		return redirect_to
+
+class LogoutView(RedirectView):
+	url = reverse_lazy("home")
+	
+	def get(self, request, *args, **kwargs):
+		logout(request)
+		return super(LogoutView, self).get(request, *args, **kwargs)
+	
+class RegisterView(CreateView):
+	model = User 
+	template_name = 'registration/register.html'
+	form_class = UserCreationForm
+	success_url = reverse_lazy("home")
+	
+	def post(self, request, *args, **kwargs):
+		self.object = None
+		form = self.get_form()
+		if form.is_valid():
+			form.save()
+			print(request.POST)
+			user = authenticate(username=request.POST["username"], password=request.POST["password1"])
+			login(request, user)
+			return HttpResponseRedirect(self.success_url)
+		else:
+			return self.form_invalid(form)
\ No newline at end of file
--- a/src/iconolab/forms/__init__.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/forms/__init__.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,10 +1,10 @@
 from django.forms import ModelForm, HiddenInput
-from iconolab.models import Annotation
+from iconolab.models import AnnotationRevision
 
 
-class AnnotationForm(ModelForm):
+class AnnotationRevisionForm(ModelForm):
 	class Meta:
-		model = Annotation
+		model = AnnotationRevision
 		fields = ('title', 'description', 'fragment',)
 		widgets = {
 			'fragment': HiddenInput(),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/forms/annotations.py	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,11 @@
+from django.forms import ModelForm, HiddenInput
+from iconolab.models import AnnotationRevision
+
+
+class AnnotationRevisionForm(ModelForm):
+    class Meta:
+        model = AnnotationRevision
+        fields = ('title', 'description', 'fragment',)
+        widgets = {
+            'fragment': HiddenInput(),
+        }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0001_initial.py	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-06-21 12:07
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Annotation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('annotation_guid', models.UUIDField(default=uuid.uuid4, editable=False)),
+                ('created', models.DateTimeField(auto_now_add=True, null=True)),
+                ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='AnnotationRevision',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('revision_guid', models.UUIDField(default=uuid.uuid4)),
+                ('title', models.CharField(max_length=255)),
+                ('description', models.TextField(null=True)),
+                ('fragment', models.TextField()),
+                ('state', models.IntegerField(choices=[(0, 'awaiting'), (1, 'accepted'), (2, 'rejected')], default=0)),
+                ('created', models.DateTimeField(auto_now_add=True, null=True)),
+                ('annotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='iconolab.Annotation')),
+                ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('merge_parent_revision', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reverse_merge_parent_revision', to='iconolab.AnnotationRevision')),
+                ('parent_revision', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reverse_parent_revision', to='iconolab.AnnotationRevision')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='AnnotationStats',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('submitted_revisions_count', models.IntegerField(blank=True, default=1, null=True)),
+                ('accepted_revisions_count', models.IntegerField(blank=True, default=1, null=True)),
+                ('contributors_count', models.IntegerField(blank=True, default=1, null=True)),
+                ('views_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('comments_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('tag_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('annotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='iconolab.Annotation')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Collection',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=50, unique=True)),
+                ('description', models.CharField(max_length=255)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Image',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=200)),
+                ('media', models.ImageField(height_field='height', upload_to='uploads/', width_field='width')),
+                ('image_ref', models.CharField(max_length=255, unique=True)),
+                ('height', models.IntegerField()),
+                ('width', models.IntegerField()),
+                ('created', models.DateTimeField(auto_now_add=True, null=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ImageStats',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('views_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('annotations_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('submitted_revisions_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('comments_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('folders_inclusion_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('tag_count', models.IntegerField(blank=True, default=0, null=True)),
+                ('image', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='iconolab.Image')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Item',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='iconolab.Collection')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ItemMetadata',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('joconde_ref', models.CharField(max_length=20, unique=True)),
+                ('domain', models.CharField(max_length=255)),
+                ('title', models.CharField(max_length=255)),
+                ('description', models.CharField(max_length=255)),
+                ('item', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='metadatas', to='iconolab.Item')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Tag',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('type', models.IntegerField(choices=[(0, 'dbpedia'), (1, 'iconolab')])),
+                ('label', models.SlugField()),
+                ('link', models.URLField(blank=True, null=True)),
+                ('description', models.CharField(max_length=255)),
+                ('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='iconolab.Collection')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='TaggingInfo',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('accuracy', models.IntegerField()),
+                ('relevancy', models.IntegerField()),
+                ('revision', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.AnnotationRevision')),
+                ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Tag')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='image',
+            name='item',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='iconolab.Item'),
+        ),
+        migrations.AddField(
+            model_name='annotationrevision',
+            name='tags',
+            field=models.ManyToManyField(blank=True, null=True, through='iconolab.TaggingInfo', to='iconolab.Tag'),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='current_revision',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='current_for_annotation', to='iconolab.AnnotationRevision'),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='image',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotations', to='iconolab.Image'),
+        ),
+        migrations.AddField(
+            model_name='annotation',
+            name='source_revision',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='source_related_annotation', to='iconolab.AnnotationRevision'),
+        ),
+    ]
--- a/src/iconolab/models.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/models.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,157 +1,295 @@
-from django.db import models
+from django.db import models, transaction
 from django.contrib.auth.models import User
 
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
-from reversion import revisions as reversion
-from reversion.models import Revision
+import uuid
 
 
 class Tag(models.Model):
+	DBPEDIA = 0
+	ICONOLAB = 1
+	TAG_TYPES = (
+		(DBPEDIA, 'dbpedia'),
+		(ICONOLAB, 'iconolab')
+	)
+	
+	type = models.IntegerField(choices=TAG_TYPES)
 	label = models.SlugField()
-	link = models.URLField()
-	description = models.TextField()
+	link = models.URLField(blank=True, null=True)
+	description = models.CharField(max_length=255)
+	collection = models.ForeignKey('Collection', blank=True, null=True)
 
-	def __str__(self):
-		return '%d:%s' % (self.id, self.label)
-
+class TaggingInfo(models.Model):
+	revision = models.ForeignKey('AnnotationRevision', on_delete=models.CASCADE)
+	tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
+	accuracy = models.IntegerField()
+	relevancy = models.IntegerField()
+	
 
 # fonds Ingres - Musee de la Poste 
 class Collection(models.Model):
-	name =	models.CharField(max_length=50)
+	name =	models.CharField(max_length=50, unique=True)
 	description = models.CharField(max_length=255)
 
 	def __str__(self):
 		return self.name
 
-# class image_metadata
+
+class ItemMetadata(models.Model):
+	item = models.OneToOneField('Item', related_name='metadatas')
+	joconde_ref = models.CharField(max_length=20, null=False, blank=False, unique=True)
+	domain = models.CharField(max_length=255)
+	title = models.CharField(max_length=255)
+	description = models.CharField(max_length=255)
+
+class Item(models.Model):
+	collection = models.ForeignKey(Collection, related_name="items")
+
+class ImageStats(models.Model):
+	image = models.OneToOneField('Image', related_name='stats', blank=False, null=False)
+	views_count = models.IntegerField(blank=True, null=True, default=0)
+	annotations_count = models.IntegerField(blank=True, null=True, default=0)
+	submitted_revisions_count = models.IntegerField(blank=True, null=True, default=0)
+	comments_count = models.IntegerField(blank=True, null=True, default=0)
+	folders_inclusion_count = models.IntegerField(blank=True, null=True, default=0)
+	tag_count = models.IntegerField(blank=True, null=True, default=0)
+
 class Image(models.Model):
 	name = models.CharField(max_length=200)
 	media = models.ImageField(upload_to='uploads/', height_field='height', width_field='width')
+	image_ref = models.CharField(max_length=255, unique=True)
+	item = models.ForeignKey('Item', related_name='images', null=True, blank=True)
 	height = models.IntegerField(null=False, blank=False)
 	width = models.IntegerField(null=False, blank=False)
-	mimetype = models.CharField(null=True, blank=True, max_length=1024)
-	exif = models.TextField(null=True, blank=True) 
-	collection = models.ForeignKey(Collection)
-	date_created = models.DateTimeField(auto_now_add=True, null=True)
-	date_modified = models.DateTimeField(auto_now=True, null=True)
+	created = models.DateTimeField(auto_now_add=True, null=True)
 	
 	def __str__(self):
 		return self.name
 
-# Folders
-class Folder(models.Model):
-	label = models.CharField(max_length=255)
-	owner = models.ForeignKey(User)
-	images = models.ManyToManyField(Image)
+# # Folders
+# class Folder(models.Model):
+# 	label = models.CharField(max_length=255)
+# 	owner = models.ForeignKey(User)
+# 	images = models.ManyToManyField(Image)
+# 
+# 	def __str__(self):
+# 		return label
+
+class AnnotationManager(models.Manager):
+	
+	# Call Annotation.objects.create_annotation to initialize a new Annotation with its associated AnnotationStats and initial AnnotationRevision
+	@transaction.atomic
+	def create_annotation(self, image, author, title='', description='', fragment='', tags=None):
+		# Create annotation object
+		new_annotation = Annotation(
+			image=image, 
+			author=author
+		)
+		new_annotation.save()
+		
+		# Create initial revision
+		initial_revision = AnnotationRevision(
+			annotation=new_annotation, 
+			author=author,
+			title=title,
+			description=description,
+			fragment=fragment,
+			state=AnnotationRevision.ACCEPTED
+		)
+		initial_revision.save()
+		new_annotation.current_revision = initial_revision
+		new_annotation.save()
+		
+		# Create stats object
+		new_annotation_stats = AnnotationStats(annotation=new_annotation)
+		new_annotation_stats.save()
+		new_annotation.stats = new_annotation_stats
+		new_annotation.save()
+
+class AnnotationStats(models.Model):
+	annotation = models.OneToOneField('Annotation', related_name='stats', blank=False, null=False)
+	submitted_revisions_count = models.IntegerField(blank=True, null=True, default=1)
+	accepted_revisions_count = models.IntegerField(blank=True, null=True, default=1)
+	contributors_count = models.IntegerField(blank=True, null=True, default=1)
+	views_count = models.IntegerField(blank=True, null=True, default=0)
+	comments_count = models.IntegerField(blank=True, null=True, default=0)
+	tag_count = models.IntegerField(blank=True, null=True, default=0)
+	
+	def contributors(self):
+		# returns users that submitted an accepted revision
+		return
+
 
-	def __str__(self):
-		return label
+class Annotation(models.Model):
+	annotation_guid = models.UUIDField(default=uuid.uuid4, editable=False)
+	image = models.ForeignKey('Image', related_name='annotations', on_delete=models.CASCADE)
+	source_revision = models.ForeignKey('AnnotationRevision', related_name='source_related_annotation', blank=True, null=True)
+	current_revision = models.OneToOneField('AnnotationRevision', related_name='current_for_annotation', blank=True, null=True)
+	author = models.ForeignKey(User, null=True)
+	created = models.DateTimeField(auto_now_add=True, null=True)
+	
+	objects = AnnotationManager()
+	
+	def update_stats(self):
+		pass
+	
+	# Call to create a new revision, possibly from a merge
+	@transaction.atomic
+	def make_new_revision(self, author, title, description, fragment, tags):
+		if author == self.author:
+			# We're creating an automatically accepted revision
+			new_revision_state = AnnotationRevision.ACCEPTED
+		else:
+			# Revision will require validation
+			new_revision_state = AnnotationRevision.AWAITING
+		new_revision = AnnotationRevision(
+			annotation = self,
+			parent_revision=self.current_revision,
+			title=title,
+			description=description,
+			author=author,
+			fragment=fragment,
+			state=new_revision_state
+		)
+		new_revision.save()
+		if new_revision.state == AnnotationRevision.ACCEPTED:
+			self.current_revision = new_revision
+			self.save()
+		return new_revision
+	
+	# Call when we're validating an awaiting revision whose parent is the current revision AS IT WAS CREATED
+	@transaction.atomic
+	def validate_existing_revision(self, revision_to_validate):
+		if revision_to_validate.parent_revision == current_revision:
+			self.current_revision = revision_to_validate
+			revision_to_validate.state = AnnotationRevision.ACCEPTED
+			revision_to_validate.save()
+			self.save()
+			
+	# Call when we're validating an awaiting revision whose parent isn't the current revision OR IF IT WAS CHANGED BY THE ANNOTATION AUTHOR
+	@transaction.atomic
+	def merge_existing_revision(self, title, description, fragment, tags, revision_to_merge):
+		merged_revision = self.make_new_revision(author=self.author, title=title, description=description, fragment=fragment, tags=tags)
+		merged_revision.merge_parent_revision = revision_to_merge
+		merged_revision.save()
+		self.current_revision=merged_revision
+		self.save()
 
-@reversion.register()
-class Annotation(models.Model):
+class AnnotationRevision(models.Model):
+	
+	AWAITING = 0
+	ACCEPTED = 1
+	REJECTED = 2
+	
+	REVISION_STATES = (
+		(AWAITING, 'awaiting'),
+		(ACCEPTED, 'accepted'),
+		(REJECTED, 'rejected')
+	)
+	
+	
+	revision_guid = models.UUIDField(default=uuid.uuid4)
+	annotation = models.ForeignKey('Annotation', related_name='revisions', null=False, blank=False)
+	parent_revision = models.ForeignKey('AnnotationRevision', related_name='reverse_parent_revision', blank=True, null=True)
+	merge_parent_revision = models.ForeignKey('AnnotationRevision', related_name='reverse_merge_parent_revision', blank=True, null=True)
+	author = models.ForeignKey(User, null=True)
 	title = models.CharField(max_length=255)
 	description = models.TextField(null=True)
 	fragment = models.TextField() # path string
-	tags = models.ManyToManyField(Tag)
-	image = models.ForeignKey(Image, default=0, on_delete=models.CASCADE)
-	author = models.ForeignKey(User, null=True)
-	date_created = models.DateTimeField(auto_now_add=True, null=True)
-	date_modified = models.DateTimeField(auto_now=True, null=True)
-
-	def __str__(self):
-		return self.title
-
-# comments
-
-LINK = 1
-IMAGE = 2
-PDF = 3
-
-COMMENT_CHOICES = (
-	(LINK, 'link'),
-	(IMAGE, 'image'),
-	(PDF, 'pdf')
-	)
-
-
-class MetaCategory(models.Model):
-	collection = models.ForeignKey(Collection)
-	label = models.CharField(max_length=200)
-
-	def __str__(self):
-		return self.label
-
-	class Meta:
-		verbose_name_plural = "Metacategories"
-
-class Comment(models.Model):
-	author =  models.ForeignKey(User)
-	created_date = models.DateTimeField(blank=False, null=False, auto_now_add=True)
-	annotation = models.ForeignKey(Annotation, null=True) # revision de l'utilisateur?
-	target = models.ForeignKey("self") #
-	#thread_id #django-contrib-comment #threadedcomment
-	content = models.TextField(blank=True)
-	metacategories = models.ManyToManyField(MetaCategory) 
-
-
-class CommentAttachement(models.Model):
-	comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
-	main_annotation = models.ForeignKey(Annotation)
-	attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=1)
-	created_date = models.DateTimeField(auto_now_add=True)
-	data = models.TextField(blank=False)
+	tags = models.ManyToManyField('Tag', through='TaggingInfo', through_fields=('revision', 'tag'), blank=True, null=True)
+	state = models.IntegerField(choices=REVISION_STATES, default=AWAITING)
+	created = models.DateTimeField(auto_now_add=True, null=True)
 
 
-# Activity & Notification
-
-class Activity(models.Model):
-
-	NEW_COMMENT = 1
-	NEW_REVISION = 2
-	NEW_REVISION_COMMENT = 3
-	NEW_EXPERT_ANSWER = 4
-	NEW_REFERENCE = 5
-	EXPERT_CALL = 6
-
-	ACTIVITY_VERBS =(
-		(NEW_COMMENT, 'Nouveau commentaire'),
-		(NEW_REVISION, 'Nouvelle revision'),
-		(NEW_REVISION_COMMENT, 'Nouveau commentaire de revision'),
-		(NEW_EXPERT_ANSWER, 'New expert ans')
-		) 
-
-	verb = models.IntegerField(choices=ACTIVITY_VERBS)
-	actor = models.ForeignKey(User)
-
-	target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
-	target_object_id = models.PositiveIntegerField()
-	target = GenericForeignKey('target_content_type', 'target_object_id')
+#class MetaCategory(models.Model):
+#	collection = models.ForeignKey(Collection)
+#	label = models.CharField(max_length=200)
+#
+#	def __str__(self):
+#		return self.label
+#
+#	class Meta:
+#		verbose_name_plural = 'Metacategories'
+#
+# 
+# class Comment(models.Model):
+# 	author =  models.ForeignKey(User)
+# 	created = models.DateTimeField(blank=False, null=False, auto_now_add=True)
+# 	annotation_revision = models.ForeignKey(AnnotationRevision, blank=True, null=True)
+# 	target = models.ForeignKey('Comment', blank=True, null=True)
+# 	content = models.TextField(blank=True)
+# 	metacategories = models.ManyToManyField(MetaCategory) 
+# 
+# 
+# class CommentAttachement(models.Model):
+# 	
+# 	LINK = 0
+# 	IMAGE = 1
+# 	PDF = 2
+# 	COMMENT_CHOICES = (
+# 		(LINK, 'link'),
+# 		(IMAGE, 'image'),
+# 		(PDF, 'pdf')
+# 	)
+# 	comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
+# 	main_annotation = models.ForeignKey(Annotation)
+# 	attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=0)
+# 	created_date = models.DateTimeField(auto_now_add=True)
+# 	data = models.TextField(blank=False)
+# 
+# 
+# # Activity & Notification
+# 
+# class Activity(models.Model):
+# 	
+# 	NEW_COMMENT = 0
+# 	NEW_REVISION = 1
+# 	NEW_COMMENT_ON_REVISION = 2
+# 	NEW_EXPERT_CALL = 3
+# 	NEW_EXPERT_ANSWER = 4
+# 	NEW_REFERENCE = 5
+# 	
+# 	ACTIVITY_VERBS = (
+# 		(NEW_COMMENT, 'New comment'),
+# 		(NEW_REVISION, 'New revision'),
+# 		(NEW_COMMENT_ON_REVISION, 'New comment on a revision'),
+# 		(NEW_EXPERT_CALL, 'New expert call'),
+# 		(NEW_EXPERT_ANSWER, 'New expert answer'),
+# 		(NEW_REFERENCE, 'New reference'),
+# 	) 
+# 
+# 	verb = models.IntegerField(choices=ACTIVITY_VERBS)
+# 	actor = models.ForeignKey(User)
+# 
+# 	target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+# 	target_object_id = models.PositiveIntegerField()
+# 	target = GenericForeignKey('target_content_type', 'target_object_id')
+# 
+# 	action_content_type = models.ForeignKey(ContentType, related_name='activity_action', on_delete=models.CASCADE, null=True, blank=True)
+# 	action_object_id = models.PositiveIntegerField(null=True, blank=True)
+# 	action_content = GenericForeignKey('action_content_type', 'action_object_id') 
+# 	
+# 	created_date = models.DateTimeField(auto_now_add=True)
+# 
+# 	def __str__(self):
+# 		return '%s:%s' % (author.name, verbe)
+# 
+# 
+# class Notification(models.Model):
+# 
+# 	UNREAD = 0
+# 	READ = 1
+# 	DELETED = 2
+# 
+# 	STATUS = (
+# 		(UNREAD, 'Unread'),
+# 		(READ, 'Read'),
+# 		(DELETED, 'Deleted')
+# 	)
+# 
+# 	activity = models.ForeignKey(Activity)
+# 	user = models.ForeignKey(User)
+# 	status = models.IntegerField(choices=STATUS, default=UNREAD)
+# 	created_date = models.DateTimeField(auto_now_add=True)
 
-	action_content_type = models.ForeignKey(ContentType, related_name='activity_action', on_delete=models.CASCADE, null=True, blank=True)
-	action_object_id = models.PositiveIntegerField(null=True, blank=True)
-	action_content = GenericForeignKey('action_content_type', 'action_object_id') 
-	
-	created_date = models.DateTimeField(auto_now_add=True)
-
-	def __str__(self):
-		return '%s:%s' % (author.name, verbe)
-
-
-class Notification(models.Model):
-
-	UNREAD = 0
-	READ = 1
-	DELETED = 2
-
-	STATUS = (
-		(READ, 'Lu'),
-		(UNREAD, 'Non lu'),
-		(DELETED, 'Efface')
-		)
-
-	activity = models.ForeignKey(Activity)
-	user = models.ForeignKey(User)
-	status = models.IntegerField(choices=STATUS, default=UNREAD)
-	created_date = models.DateTimeField(auto_now_add=True)
-
--- a/src/iconolab/settings/__init__.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/settings/__init__.py	Tue Jun 21 14:38:09 2016 +0200
@@ -71,7 +71,6 @@
     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'reversion.middleware.RevisionMiddleware',
 ]
 
 
--- a/src/iconolab/signals/handlers.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/signals/handlers.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,32 +0,0 @@
-from iconolab.models import Comment, Annotation
-from django.db.models import signals
-from django.dispatch import receiver
-from pprint import pprint
-from notifications.signals import notify
-from iconolab.utils.utils import NotificationManager
-import pprint
-
-
-#handle metacategories here
-'''
-
-'''
-
-@receiver(signals.post_save, sender=Comment)
-def handle_metacategory(sender, **kwargs):
-	commentInstance = kwargs.get('instance')
-	if commentInstance is not None:
-		metacategories_list = [mcat.label for mcat in commentInstance.metacategories.all()]
-		print(", ".join(metacategories_list))
-
-
-@receiver(signals.post_save, sender=Annotation)
-def handle_new_annotation(sender, **kwargs):
-	object_instance = kwargs.get('instance')
-	if object_instance is None:
-		pass
-
-	notification = NotificationManager.create_notification(object_instance.author, verb=NotificationManager.NEW_ANNOTATION)
-	NotificationManager.notify(notification=notification)
-
-
--- a/src/iconolab/templates/iconolab/annotation.html	Mon Jun 20 15:05:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-
-{% block page_js %}
-	<script src="{% static 'iconolab/js/dist/bundle.js' %}" type="text/javascript"></script>
-{% endblock%}
-
-{% block content %}
-	<div>
-		
-		<div class="col-md-4">
-			<div id="fragmentContainer">
-			{% thumbnail annotation.image.media "100x100" crop="center" as im %}
-    				<img src=" {{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-    		{% empty %}
-    			<p>No image</p>
-			{% endthumbnail %}
-			</div>
-		</div>
-
-		<div class="col-md-4">
-
-			<div id='iconolab-image' style='position:relative'>
-				<img src="{% static annotation.image.media.url %}" 
-				width="{{ annotation.image.width }}" 
-				height="{{ annotation.image.height }}" />
-				<!-- deal with path here -->
-				<svg class='cut-canvas' style='border: 1px solid red;'></svg>
-				<path class='image-path' d='M150 0 L75 200 L225 200 Z' />
-			</div>
-
-			<div sytle='border: 1px solid red; width: 300px; height:500px'>
-
-				{% thumbnail annotation.image.media "100x100" crop="center" as im %}
-    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-				{% endthumbnail %}
-
-				<p>Test iri</p>
-			</div>
-
-		</div>
-
-	</div>
-{% endblock %}
-
-{% block footer_js %}
-	<script>
-		iconolab.initCutoutComponent({ imageId: '#iconolab-image' });
-	</script>
-{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/change_annotation.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,173 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% block content %}
+
+	<div id="drawing-zone" class="row" style="padding-top: 10px; border:1px solid orange">
+		
+		<div v-show='!formView' class="editor-wrapper col-md-12">
+			<div class='col-md-2'>
+				<ul class="form-drawing-wrapper list-inline">
+					<p class='form-drawing pullright'><strong>Type de dessin</strong></p>
+					<li @click="setDrawingMode('RECT')" v-bind:class="{ 'selected': isRect }" class='pull-md-right drawingModeBtn'>Rect.</li>
+					<li @click="setDrawingMode('FREE')" v-bind:class="{ 'selected': !isRect }" class='pull-md-right drawingModeBtn'>Libre</li>
+				</ul>
+
+				<ul class='form-drawing-wrapper list-inline'>
+					<p class='form-drawing pullright'><strong>Actions</strong></p>
+					
+					<li @click="clear" class='pull-md-right drawingModeBtn'><i class='fa fa-trash'></i> Effacer</li>
+
+					<li @click="save" class='pull-md-right drawingModeBtn'><i class='fa fa-plus'></i> Créer le fragment</li>
+				</ul>
+			</div>
+			
+			<div class="col-md-8">
+				<div v-el:image id="iconolab-image-wrapper">
+					{% thumbnail annotation.image.media "x800" crop="center" as im %}
+	    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+	    				<path class="image-path" d="{{ annotation.current_revision.fragment }}"></path>
+					{% endthumbnail %}
+				</div>
+			</div>
+
+		</div>
+
+		<div v-show="formView" class="col-md-12">
+			
+			<div class="col-xs-6">
+				<div class="small-image-wrapper" style="position: relative">
+					{% thumbnail annotation.image.media "x300" crop="center" as im %}
+						<img v-el:small-image @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+
+						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
+							
+							<defs>
+								<mask xmlns="http://www.w3.org/2000/svg" id="smallImage">
+									<rect x="0" y="0" width="{{ im.width }}", height="{{ im.height }}" fill="white"/>
+									<g v-bind:transform="transformMatrix">
+										<path v-bind:d="fragmentPath"></path>
+									</g>
+								</mask>	
+							</defs>
+
+							<g v-show="!displayMask" v-bind:transform="transformMatrix">
+								<path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
+							</g>
+
+							<rect v-show="displayMask" v-el:small-mask x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
+						</svg>
+						
+					{% endthumbnail %}
+				</div>
+				<ul class="inline">
+					<li @click="showEditor" class="showPointer"> <i class='fa fa-edit'></i> Editer le fragment</li>
+					<li v-show="!displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Afficher la zone</li>
+					<li v-show="displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Masquer la zone</li>
+				</ul>
+			</div>
+
+			<div class='col-xs-6' style="">
+				 <form method="POST" class="post-form">
+				 	{% csrf_token %}
+        			{{ form.as_p }}
+        			<input v-model="normalizePath" type="hidden" name="fragment"></input>
+        			<button type="submit" class="save btn btn-default">Enregister</button>
+    			</form>
+			</div>
+
+		</div>
+	</div>
+
+{% endblock %}
+
+{% block footer_js %}
+	<script>
+		var drawingVue = new Vue({
+			el: '#drawing-zone',
+			MODE_RECT: 'RECT',
+			MODE_FREE: 'FREE', 
+
+			data: {
+				mode:"",
+				isRect: true,
+				normalizePath: "",
+				readOnly: false,
+				formView: false,
+				useClipPath: false,
+				transformMatrix: "",
+				fragmentPath: "",
+				displayMask: false
+
+			},
+
+			init: function () {
+				var self = this;
+				this.drawingComponent = iconolab.initCutoutComponent({ 
+					wrapperId: '#iconolab-image-wrapper',
+						actionWrapper: '#action-wrapper',
+						readOnly: false,
+						onDrawingModeChange: function (mode) {
+							self.$nextTick(function () {
+								self.setDrawingMode(mode, false);
+							});
+						}
+				});
+			},
+
+			methods: {
+
+				setDrawingMode: function (mode, updateComponent) {
+					var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
+					this.mode = this.$options['MODE_' + mode];
+					this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
+					if (updateComponent) {
+						this.drawingComponent.setDrawingMode(this.mode);
+					}
+				},
+
+				showEditor: function () {
+					this.formView = false;
+					console.log(this.$els.smallImage);
+				},
+
+				highLightZone: function () {
+					if (!this.displayMask) {
+						this.displayMask = true;
+					}
+					else {
+						this.displayMask = false;
+					}
+					//this.maskFill = "orange";
+					//this.fragmentFill = "none";
+				},
+
+				displayEditedPath: function () {
+					/* path to save */
+					var normalizePath = this.drawingComponent.getPath();
+				},
+
+				save: function () {
+					this.normalizePath = this.drawingComponent.getPath();
+					var smallImage = this.$els.smallImage;
+					this.formView = true;
+					/* 100x = smallImageHeight && 100x=smallImageWidth | 100 = ViewBoxBound */
+					var xRatio = smallImage.width / 100;
+					var yRatio = smallImage.height / 100;
+					var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
+					this.transformMatrix ="matrix(" + transformMatrix + ")";
+					this.fragmentPath = this.normalizePath.split(';')[0];
+					console.log(this.fragmentPath);
+					console.log(this.transformMatrix);
+				},
+
+				clear: function () {
+					this.drawingComponent.clear();
+				}
+			}
+		}); 
+	</script>
+{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/collection_home.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}collectionhome{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/create_annotation.html	Mon Jun 20 15:05:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-{% extends 'iconolab_base.html'%}
-
-{% block content %}
-
-<h2>Créer un annotation</h2>
-<div class='col-md-8'>
-	<p> Afficher le formulaire de création de fragment </p>
-</div>
-
-{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/detail_annotation.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,42 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}
+	<div id="annotation-wrapper" class="row" style="border: 1px solid gray">
+		
+		<div v-show="formView" class="col-md-12">
+			<div class="col-xs-6">
+				<div class="small-image-wrapper" style="position: relative">
+					{% thumbnail annotation.image.media "x300" crop="center" as im %}
+						<img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
+
+						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
+
+							<g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
+								<path d="{{ annotation.current_revision.fragment }}" opacity="0.7" fill="orange"></path>
+							</g>
+
+						</svg>
+						
+					{% endthumbnail %}
+				</div>
+			</div>
+
+			<div class='col-xs-6' style="">
+				<p> <strong>Title:</strong> {{ annotation.current_revision.title }}</p>
+				<p> <strong>Description:</strong> {{ annotation.current_revision.description }}</p>
+				<p> <strong>Tags:</strong> {{ annotation.current_revision.tags }}</p>
+				<p> <strong>Fragment:</strong> {{ annotation.current_revision.fragment }}</p>
+
+				<a href="{% url 'annotation_edit' collection_name image_ref annotation_guid  %}">Editer</a> | 
+				<a href="{% url 'annotation_edit' collection_name image_ref annotation_guid %}">Proposer une révision</a>
+			</div>
+		</div>
+	</div>
+
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/detail_image.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}detailimage{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/edit_annotation.html	Mon Jun 20 15:05:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-{% block content %}
-
-	<div id="drawing-zone" class="row" style="padding-top: 10px; border:1px solid orange">
-		
-		<div v-show='!formView' class="editor-wrapper col-md-12">
-			<div class='col-md-2'>
-				<ul class="form-drawing-wrapper list-inline">
-					<p class='form-drawing pullright'><strong>Type de dessin</strong></p>
-					<li @click="setDrawingMode('RECT')" v-bind:class="{ 'selected': isRect }" class='pull-md-right drawingModeBtn'>Rect.</li>
-					<li @click="setDrawingMode('FREE')" v-bind:class="{ 'selected': !isRect }" class='pull-md-right drawingModeBtn'>Libre</li>
-				</ul>
-
-				<ul class='form-drawing-wrapper list-inline'>
-					<p class='form-drawing pullright'><strong>Actions</strong></p>
-					
-					<li @click="clear" class='pull-md-right drawingModeBtn'><i class='fa fa-trash'></i> Effacer</li>
-
-					<li @click="save" class='pull-md-right drawingModeBtn'><i class='fa fa-plus'></i> Créer le fragment</li>
-				</ul>
-			</div>
-			
-			<div class="col-md-8">
-				<div v-el:image id="iconolab-image-wrapper">
-					{% thumbnail annotation.image.media "x800" crop="center" as im %}
-	    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-	    				<path class="image-path" d="{{ annotation.fragment }}"></path>
-					{% endthumbnail %}
-				</div>
-			</div>
-
-		</div>
-
-		<div v-show="formView" class="col-md-12">
-			
-			<div class="col-xs-6">
-				<div class="small-image-wrapper" style="position: relative">
-					{% thumbnail annotation.image.media "x300" crop="center" as im %}
-						<img v-el:small-image @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-
-						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
-							
-							<defs>
-								<mask xmlns="http://www.w3.org/2000/svg" id="smallImage">
-									<rect x="0" y="0" width="{{ im.width }}", height="{{ im.height }}" fill="white"/>
-									<g v-bind:transform="transformMatrix">
-										<path v-bind:d="fragmentPath"></path>
-									</g>
-								</mask>	
-							</defs>
-
-							<g v-show="!displayMask" v-bind:transform="transformMatrix">
-								<path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
-							</g>
-
-							<rect v-show="displayMask" v-el:small-mask x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
-						</svg>
-						
-					{% endthumbnail %}
-				</div>
-				<ul class="inline">
-					<li @click="showEditor" class="showPointer"> <i class='fa fa-edit'></i> Editer le fragment</li>
-					<li v-show="!displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Afficher la zone</li>
-					<li v-show="displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Masquer la zone</li>
-				</ul>
-			</div>
-
-			<div class='col-xs-6' style="">
-				 <form method="POST" class="post-form">
-				 	{% csrf_token %}
-        			{{ form.as_p }}
-        			<input v-model="normalizePath" type="hidden" name="fragment"></input>
-        			<button type="submit" class="save btn btn-default">Enregister</button>
-    			</form>
-			</div>
-
-		</div>
-	</div>
-
-{% endblock %}
-
-{% block footer_js %}
-	<script>
-		var drawingVue = new Vue({
-			el: '#drawing-zone',
-			MODE_RECT: 'RECT',
-			MODE_FREE: 'FREE', 
-
-			data: {
-				mode:"",
-				isRect: true,
-				normalizePath: "",
-				readOnly: false,
-				formView: false,
-				useClipPath: false,
-				transformMatrix: "",
-				fragmentPath: "",
-				displayMask: false
-
-			},
-
-			init: function () {
-				var self = this;
-				this.drawingComponent = iconolab.initCutoutComponent({ 
-					wrapperId: '#iconolab-image-wrapper',
-						actionWrapper: '#action-wrapper',
-						readOnly: false,
-						onDrawingModeChange: function (mode) {
-							self.$nextTick(function () {
-								self.setDrawingMode(mode, false);
-							});
-						}
-				});
-			},
-
-			methods: {
-
-				setDrawingMode: function (mode, updateComponent) {
-					var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
-					this.mode = this.$options['MODE_' + mode];
-					this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
-					if (updateComponent) {
-						this.drawingComponent.setDrawingMode(this.mode);
-					}
-				},
-
-				showEditor: function () {
-					this.formView = false;
-					console.log(this.$els.smallImage);
-				},
-
-				highLightZone: function () {
-					if (!this.displayMask) {
-						this.displayMask = true;
-					}
-					else {
-						this.displayMask = false;
-					}
-					//this.maskFill = "orange";
-					//this.fragmentFill = "none";
-				},
-
-				displayEditedPath: function () {
-					/* path to save */
-					var normalizePath = this.drawingComponent.getPath();
-				},
-
-				save: function () {
-					this.normalizePath = this.drawingComponent.getPath();
-					var smallImage = this.$els.smallImage;
-					this.formView = true;
-					/* 100x = smallImageHeight && 100x=smallImageWidth | 100 = ViewBoxBound */
-					var xRatio = smallImage.width / 100;
-					var yRatio = smallImage.height / 100;
-					var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
-					this.transformMatrix ="matrix(" + transformMatrix + ")";
-					this.fragmentPath = this.normalizePath.split(';')[0];
-					console.log(this.fragmentPath);
-					console.log(this.transformMatrix);
-				},
-
-				clear: function () {
-					this.drawingComponent.clear();
-				}
-			}
-		}); 
-	</script>
-{% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/home.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}globalhome{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/show_annotation.html	Mon Jun 20 15:05:23 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-{% load iconolab_tags %}
-
-{% block content %}
-	<div id="annotation-wrapper" class="row" style="border: 1px solid gray">
-		
-		<div v-show="formView" class="col-md-12">
-			<div class="col-xs-6">
-				<div class="small-image-wrapper" style="position: relative">
-					{% thumbnail annotation.image.media "x300" crop="center" as im %}
-						<img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
-
-						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
-
-							<g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
-								<path d="{{ annotation.fragment }}" opacity="0.7" fill="orange"></path>
-							</g>
-
-						</svg>
-						
-					{% endthumbnail %}
-				</div>
-			</div>
-
-			<div class='col-xs-6' style="">
-				<p> <strong>Title:</strong> {{ annotation.title }}</p>
-				<p> <strong>Description:</strong> {{ annotation.description }}</p>
-				<p> <strong>Tags:</strong> {{ annotation.tags }}</p>
-				<p> <strong>Fragment:</strong> {{ annotation.fragment }}</p>
-
-				<a href="{% url 'annotation_edit' annotation.id %}">Editer</a> | 
-				<a href="{% url 'annotation_edit' annotation.id %}">Proposer une révision</a>
-			</div>
-		</div>
-	</div>
-
-{% endblock %}
--- a/src/iconolab/templates/partials/header.html	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/templates/partials/header.html	Tue Jun 21 14:38:09 2016 +0200
@@ -1,33 +1,38 @@
 <nav class="navbar navbar-default" style="">
-        <div class="container-fluid">
-          <div class="navbar-header">
-            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-              <span class="sr-only">{{ greeting }}</span>
-              <span class="icon-bar"></span>
-              <span class="icon-bar"></span>
-              <span class="icon-bar"></span>
-            </button>
-            <a class="navbar-brand" href="#">Iconolab</a>
-          </div>
-          <div id="navbar" class="navbar-collapse collapse">
-            <ul class="nav navbar-nav">
-              <li class="active"><a href="#">Accueil</a></li>
-              <li><a href="#">Le projet</a></li>
-              <li><a href="#">Contribuer</a></li>
-            </ul>
-            
-            <form class="navbar-form navbar-left" role="search">
-              <div class="form-group">
-                <input type="text" class="form-control" placeholder="Trouver une image...">
-              </div>
-              <button type="submit" class="btn btn-default">Rechercher</button>
-            </form>
+  <div class="container-fluid">
+    <div class="navbar-header">
+      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+        <span class="sr-only">{{ greeting }}</span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </button>
+      <a class="navbar-brand" href="#">Iconolab</a>
+    </div>
+    <div id="navbar" class="navbar-collapse collapse">
+      <ul class="nav navbar-nav">
+        <li class="active"><a href="#">Accueil</a></li>
+        <li><a href="#">Le projet</a></li>
+        <li><a href="#">Contribuer</a></li>
+      </ul>
+      
+      <form class="navbar-form navbar-left" role="search">
+        <div class="form-group">
+          <input type="text" class="form-control" placeholder="Trouver une image...">
+        </div>
+        <button type="submit" class="btn btn-default">Rechercher</button>
+      </form>
 
-            <ul class="nav navbar-nav navbar-right">
-                <li role="presentation"><a href="#">Notifications <span class="badge">3</span></a></li>
-              <li><a href="{% url 'account:register' %}">Créer un compte</a></li>
-              <li><a href="{% url 'account:login' %}">Se Connecter</a></li>
-            </ul>
-          </div><!--/.nav-collapse -->
-        </div><!--/.container-fluid -->
-      </nav>
\ No newline at end of file
+      <ul class="nav navbar-nav navbar-right">
+          <li role="presentation"><a href="#">Notifications <span class="badge">3</span></a></li>
+        {% if user.is_authenticated %}
+          <li><a href="#">{{user.username}}: Mon espace</a></li>
+          <li><a href="{% url 'account:logout' %}">Se déconnecter</a></li>
+        {% else %}
+          <li><a href="{% url 'account:register' %}">Créer un compte</a></li>
+          <li><a href="{% url 'account:login' %}">Se Connecter</a></li>
+        {% endif %}
+      </ul>
+    </div><!--/.nav-collapse -->
+  </div><!--/.container-fluid -->
+</nav>
\ No newline at end of file
--- a/src/iconolab/templates/registration/login.html	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/templates/registration/login.html	Tue Jun 21 14:38:09 2016 +0200
@@ -17,25 +17,19 @@
 
 <div class='col-md-6 center centerer'>
     <h3>Se connecter</h3>
-    <form method="post" action="{% url 'account:login' %}">
-    {% csrf_token %}
-      
-      <div class="form-group">
-        <label for="exampleInputEmail1">{{form.username.label_tag}}</label>
-        <input type="text" class="form-control" id="username" placeholder="Login ou utilisateur">
-      </div>
-
-      <div class="form-group">
-        <label for="exampleInputPassword1">{{form.password.label_tag}}</label>
-        <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
-      </div>
-
-      <input type="hidden" name="next" value="{{ next }}" />
-      <button type="submit" class="btn btn-default">Submit</button>
-      <p><a href="{% url 'account:password_reset' %}">Lost password?</a></p>
-
+    <form class="form" action="{% url 'account:login' %}" method="POST">
+      {% csrf_token %} 
+      {% for field in form %}
+      <fieldset class="form-group">
+        <label class="control-label" for="id_{{ field.name }}">{{ field.label }}</label>
+        <input type="{% if field.label == "Username" %}text{% else %}password{% endif %}" class="form-control"
+          name="{{ field.name }}"
+          id="id_{{ field.name }}" > 
+        {% if field.errors %}{{ field.errors }}{% endif %}
+      </fieldset>
+      {% endfor %}
+      <input type="submit" value="Submit"  class="btn btn-primary">
     </form>
-
 </div>
 
 {% endblock %}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/registration/register.html	Tue Jun 21 14:38:09 2016 +0200
@@ -0,0 +1,22 @@
+{% extends "iconolab_base.html" %}
+
+{% block content %}
+
+<div class='col-md-6 center centerer'>
+    <h3>S'enregistrer</h3>
+    <form class="form" action="{% url 'account:register' %}" method="POST">
+      {% csrf_token %} 
+      {% for field in form %}
+      <fieldset class="form-group">
+        <label class="control-label" for="id_{{ field.name }}">{{ field.label }}</label>
+        <input type="{% if field.label == "Username" %}text{% else %}password{% endif %}" class="form-control"
+          name="{{ field.name }}"
+          id="id_{{ field.name }}" > 
+        {% if field.errors %}{{ field.errors }}{% else %}<p class="help-text">{{ field.help_text }}</p>{% endif %}
+      </fieldset>
+      {% endfor %}
+      <input type="submit" value="Submit"  class="btn btn-primary">
+    </form>
+</div>
+
+{% endblock %}
\ No newline at end of file
--- a/src/iconolab/urls.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/urls.py	Tue Jun 21 14:38:09 2016 +0200
@@ -15,23 +15,23 @@
 """
 from django.conf.urls import url, include
 from django.contrib import admin
-from notifications import urls
 from . import views
 from . import settings
 
 from django.conf.urls.static import static
+from django.contrib.auth.decorators import login_required
 from django.contrib.staticfiles.urls import staticfiles_urlpatterns
 
 urlpatterns = [
     url(r'^admin/', admin.site.urls),
-    url(r'^home', views.index, name="home"),
-    url(r'^annotation/(?P<pk>[0-9]+)/$', views.show_annotation, name='annotation_show'),
-    url(r'^annotation/(?P<pk>[0-9]+)/fragment', views.draw_fragment, name='annotation_fragment'),
-    url(r'^annotation/create', views.create_annotation, name='annotation_create'),
-    url(r'^annotation/(?P<pk>[0-9]+)/edit', views.edit_annotation, name='annotation_edit'),
-
+    url(r'^home$', views.GlobalHomepageView.as_view(), name="home"),
+    url(r'^collections/(?P<collection_name>[a-z]+)$', views.CollectionHomepageView.as_view(), name='collection_home'), # Home fond
+    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)$', views.ShowImageView.as_view(), name='image_detail'),
+    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'),
+    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'),
+    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'),
+    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[0-9]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'),
     url(r'^rest', include('restapi.urls')),
-    url(r'^inbox/notifications/', include(urls, namespace='notifications')),
     url(r'^account/', include('iconolab.auth.urls', namespace='account')),
 ]
 
--- a/src/iconolab/views.py	Mon Jun 20 15:05:23 2016 +0200
+++ b/src/iconolab/views.py	Tue Jun 21 14:38:09 2016 +0200
@@ -1,46 +1,128 @@
-from django.shortcuts import HttpResponse, get_object_or_404, render, redirect
-from iconolab.models import Annotation
+from django.shortcuts import HttpResponse, get_object_or_404, render
+from iconolab.models import Annotation, Collection
 from django.http import Http404
 from django.contrib.auth.decorators import login_required
-from .forms import AnnotationForm
+from django.views.generic import View, RedirectView
+from django.views.generic.base import ContextMixin
+from django.core.urlresolvers import reverse
+from .forms.annotations import AnnotationRevisionForm
 from pprint import pprint
 
-@login_required
-def index(request):
-	print(request.user)
-	return HttpResponse("<p>Home</p>");
+class GlobalHomepageView(View):
+	def get(self, request, *args, **kwargs):
+		# Handle homepage view here
+		return render(request, 'iconolab/home.html');
+
+
+class CollectionHomepageView(View, ContextMixin):
+	def get(self, request, *args, **kwargs):
+		context = super(CollectionHomepageView, self).get_context_data(**kwargs)
+		context["collection_name"] = self.kwargs.get("collection_name", "")
+		return render(request, 'iconolab/collection_home.html', context);
+
+
+class ShowImageView(View):
+	def get(self, request, *args, **kwargs):
+		context = super(CollectionHomepageView, self).get_context_data(**kwargs)
+		context["collection_name"] = self.kwargs.get("collection_name", "")
+		context["image_ref"] = self.kwargs.get("image_ref", "")
+		return render(request, 'iconolab/collection_home.html', context);
 
 
-def draw_fragment(request, pk):
-	annotation = get_object_or_404(Annotation, pk=pk)
-	return render(request, 'iconolab/fragment_draw.html', {'annotation': annotation})
+class ShowAnnotationView(View, ContextMixin):
+	
+	def get_context_data(self, **kwargs):
+		context = super(ShowAnnotationView, self).get_context_data(**kwargs)
+		context["collection_name"] = self.kwargs.get("collection_name", "")
+		context["image_ref"] = self.kwargs.get("image_ref", "")
+		context["annotation_guid"] = self.kwargs.get("annotation_guid", "")
+		return context
+	
+	def check_kwargs(self, kwargs):
+		try:
+			collection = Collection.objects.get(name=kwargs.get("collection_name", ""))
+		except Collection.DoesNotExist:
+			return RedirectView.as_view(url=reverse("404error"))
+		try:
+			annotation = Annotation.objects.get(annotation_guid=kwargs.get("annotation_guid", ""))
+		except Annotation.DoesNotExist:
+			return RedirectView.as_view(url=reverse("404error"))
+		return collection, annotation
+	
+	def get(self, request, *args, **kwargs):
+		collection, annotation = self.check_kwargs(kwargs)
+		self.check_kwargs(kwargs)
+		context = self.get_context_data(**kwargs)
+		context["annotation"] = annotation
+		print(annotation)
+		return render(request, 'iconolab/detail_annotation.html', context)
 
-def create_annotation(request):
-	return render(request, 'iconolab/create_annotation.html')
-	pass
-
-def show_annotation(request, pk):
-	annotation = get_object_or_404(Annotation, pk=pk)
-	return render(request, 'iconolab/show_annotation.html', {'annotation': annotation}) 
+    
+class CreateAnnotationView(View):
+	
+	def get(self, request, *args, **kwargs):
+		print("test")
+		return render(request, 'iconolab/change_annotation.html')
+	
+	def post(self, request, *args, **kwargs):
+		# Handle annotation submit here
+		pass
+	
+class EditAnnotationView(View, ContextMixin):
+	
+	def get_context_data(self, **kwargs):
+		context = super(EditAnnotationView, self).get_context_data(**kwargs)
+		context["collection_name"] = self.kwargs.get("collection_name", "")
+		context["image_ref"] = self.kwargs.get("image_ref", "")
+		context["annotation_guid"] = self.kwargs.get("annotation_guid", "")
+		return context
+	
+	def check_kwargs(self, kwargs):
+		try:
+			collection = Collection.objects.get(name=kwargs.get("collection_name", ""))
+		except Collection.DoesNotExist:
+			return RedirectView.as_view(url=reverse("404error"))
+		try:
+			annotation = Annotation.objects.get(annotation_guid=kwargs.get("annotation_guid", ""))
+		except Annotation.DoesNotExist:
+			return RedirectView.as_view(url=reverse("404error"))
+		return collection, annotation
+	
+	def get(self, request, *args, **kwargs):
+		collection, annotation = self.check_kwargs(kwargs)
+		annotation_form = AnnotationRevisionForm()
+		context = self.get_context_data(**kwargs)
+		context["annotation"] = annotation
+		context["form"] = annotation_form
+		return render(request, 'iconolab/change_annotation.html', context) 
+	
+	def post(self, request, *args, **kwargs):
+		collection, annotation = self.check_kwargs(kwargs)
+		collection_name = kwargs["collection_name"]
+		image_ref = kwargs["image_ref"]
+		annotation_guid = kwargs["annotation_guid"]
+		annotation_form = AnnotationRevisionForm(request.POST)
+		if annotation_form.is_valid():
+			revision_author = request.user
+			revision_title = annotation_form.cleaned_data["title"]
+			revision_description = annotation_form.cleaned_data["description"]
+			revision_fragment = annotation_form.cleaned_data["fragment"]
+			annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, None)
+			return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_ref': image_ref, 'annotation_guid': annotation_guid}))(request)
 
 
-# save annotation
-# handle revision
-def save_annotation(request):
-	pass
-
-def edit_annotation(request, pk):
-
-	#enregistrer title, description, fragment:nmz
-	annotation = get_object_or_404(Annotation, pk=pk)
-
-	if request.method == 'POST':
-		annotation_form = AnnotationForm(request.POST, instance=annotation)
-		if annotation_form.is_valid():
-			annotation = annotation_form.save()
-			return redirect('iconolab.views.show_annotation', pk=pk)	
-	else:
-		Annotation_form = AnnotationForm(instance = annotation)
-
-	return render(request, 'iconolab/edit_annotation.html', {'annotation': annotation, 'form': Annotation_form }) 
-	
\ No newline at end of file
+class MergeProposalView(View):
+	
+	def get(self, request, *args, **kwargs):
+		# Handle merge form display here
+		pass
+	
+	def post(self, request, *args, **kwargs):
+		# Handle merge form submit here
+		pass
+	
+	
+class NotFoundErrorView(View):
+	def get(self, request, *args, **kwargs):
+		# Handle image display here
+		pass
\ No newline at end of file