Merge with e13fed7f0837208731b2f9b1472fe2284966120d
authordurandn
Thu, 18 Aug 2016 10:58:55 +0200
changeset 137 454e39dced1f
parent 136 81d82b1f431a (diff)
parent 125 e13fed7f0837 (current diff)
child 138 2c2d394904db
child 143 c68983a2efac
Merge with e13fed7f0837208731b2f9b1472fe2284966120d
src/iconolab/models.py
src/iconolab/settings/dev.py.tmpl
src/iconolab/urls.py
--- a/src/iconolab/__init__.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/__init__.py	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,46 @@
+VERSION = (0, 0, 1, "alpha", 0)
+
+VERSION_STR = ".".join(map(lambda i:"%02d" % (i,), VERSION[:2]))
+
+###
+# https://github.com/django/django/blob/1.9.1/django/utils/version.py
+#
+def get_version(version):
+    "Returns a PEP 440-compliant version number from VERSION."
+    version = get_complete_version(version)
+
+    # Now build the two parts of the version number:
+    # main = X.Y[.Z]
+    # sub = .devN - for pre-alpha releases
+    #     | {a|b|rc}N - for alpha, beta, and rc releases
+
+    main = get_main_version(version)
+
+    sub = ''
+    if version[3] == 'alpha' and version[4] == 0:
+        sub = '.dev'
+
+    elif version[3] != 'final':
+        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
+        sub = mapping[version[3]] + str(version[4])
+
+    return str(main + sub)
+
+def get_complete_version(version):
+    """
+    then checks for correctness of the tuple provided.
+    """
+    assert len(version) == 5
+    assert version[3] in ('alpha', 'beta', 'rc', 'final')
+
+    return version
+
+def get_main_version(version=None):
+    "Returns main version (X.Y[.Z]) from VERSION."
+    version = get_complete_version(version)
+    parts = 2 if version[2] == 0 else 3
+    return '.'.join(str(x) for x in version[:parts])
+
+__version__ = get_version(VERSION)
+
+default_app_config = 'iconolab.apps.IconolabApp'
--- a/src/iconolab/admin.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/admin.py	Thu Aug 18 10:58:55 2016 +0200
@@ -1,1 +1,20 @@
-from django.contrib import admin
\ No newline at end of file
+from django.contrib import admin
+from iconolab import models
+
+# Iconolab objects
+admin.site.register(models.Collection)
+admin.site.register(models.Item)
+admin.site.register(models.Image)
+admin.site.register(models.Annotation)
+admin.site.register(models.AnnotationRevision)
+# Tags
+admin.site.register(models.Tag)
+admin.site.register(models.TaggingInfo)
+
+# User
+admin.site.register(models.UserProfile)
+
+# Comment system
+admin.site.register(models.IconolabComment)
+admin.site.register(models.MetaCategory)
+admin.site.register(models.MetaCategoryInfo)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0010_auto_20160816_1242.py	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-08-16 12:42
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0009_auto_20160812_1417'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='collection',
+            name='description',
+            field=models.TextField(null=True),
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0011_userprofile.py	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-08-16 14:46
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('iconolab', '0010_auto_20160816_1242'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='UserProfile',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('administers_collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='collection', to='iconolab.Collection')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0012_auto_20160817_1019.py	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-08-17 10:19
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0011_userprofile'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='userprofile',
+            name='user',
+            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
--- a/src/iconolab/models.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/models.py	Thu Aug 18 10:58:55 2016 +0200
@@ -19,18 +19,23 @@
     def is_internal(self):
         return self.link.startswith(settings.INTERNAL_TAGS_URL)
 
+    def __str__(self):
+        return self.label_slug+":"+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()
+    
+    def __str__(self):
+        return self.tag.label_slug+":to:"+self.revision.revision_guid
 
 
 class Collection(models.Model):
     name = models.SlugField(max_length=50, unique=True)
     verbose_name = models.CharField(max_length=50, null=True, blank=True)
-    description = models.CharField(max_length=255)
+    description = models.TextField(null=True)
     image = models.ImageField(upload_to='uploads/', height_field='height', width_field='width', null=True, blank=True)
     height = models.IntegerField(null=True, blank=True)
     width = models.IntegerField(null=True, blank=True)
@@ -42,7 +47,9 @@
 class Item(models.Model):
     collection = models.ForeignKey(Collection, related_name="items")
     item_guid = models.UUIDField(default=uuid.uuid4, editable=False)
-
+    
+    def __str__(self):
+        return str(self.item_guid)+":from:"+self.collection.name
     
 class ItemMetadata(models.Model):
     item = models.OneToOneField('Item', related_name='metadatas')
@@ -61,7 +68,10 @@
     
     @property
     def get_joconde_url(self):
-        return self.joconde_ref
+        return settings.JOCONDE_NOTICE_BASE_URL+self.joconde_ref.rjust(11, '0')
+    
+    def __str__(self):
+        return "metadatas:for:"+str(self.item.item_guid)
 
 
 class ImageStats(models.Model):
@@ -73,6 +83,9 @@
     folders_inclusion_count = models.IntegerField(blank=True, null=True, default=0)
     tag_count = models.IntegerField(blank=True, null=True, default=0)
     
+    def __str__(self):
+        return "stats:for:"+self.image.image_guid
+    
     def set_tags_stats(self):
         self.tag_count = Tag.objects.filter(tagginginfo__revision__annotation__image = self.image).distinct().count()
     
@@ -106,7 +119,10 @@
     height = models.IntegerField(null=False, blank=False)
     width = models.IntegerField(null=False, blank=False)
     created = models.DateTimeField(auto_now_add=True, null=True)
-
+    
+    def __str__(self):
+        return str(self.image_guid)+":"+self.name
+    
     @property
     def collection(self):
         return self.item.collection.name
@@ -139,9 +155,6 @@
     def measurements(self):
         return self.item.metadatas.measurements
 
-    def __str__(self):
-        return self.name
-
     @property
     def tags(self):
         tag_list = []
@@ -183,6 +196,7 @@
         new_annotation.current_revision = initial_revision
         new_annotation.stats = new_annotation_stats
         new_annotation.save()
+        iconolab_signals.revision_created.send(sender=AnnotationRevision, instance=initial_revision)
         return new_annotation
 
 
@@ -196,6 +210,9 @@
     comments_count = models.IntegerField(blank=True, null=True, default=0)
     tag_count = models.IntegerField(blank=True, null=True, default=0)
     
+    def __str__(self):
+        return "stats:for:"+str(self.annotation_guid)
+    
     @property
     def contributors(self):
         user_ids_list = self.annotation.revisions.filter(state__in=[AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]).values_list("author__id", flat=True)
@@ -241,6 +258,9 @@
     
     objects = AnnotationManager()
     
+    def __str__(self):
+        return str(self.annotation_guid)+":"+self.current_revision.title
+        
     @property
     def awaiting_revisions_count(self):
         return self.revisions.filter(state=AnnotationRevision.AWAITING).distinct().count()
@@ -338,6 +358,9 @@
     state = models.IntegerField(choices=REVISION_STATES, default=AWAITING)
     created = models.DateTimeField(auto_now_add=True, null=True)
 
+    def __str__(self):
+        return str(self.revision_guid)+":"+self.title
+
     def set_tags(self, tags_json_string):
         try:
             tags_dict = json.loads(tags_json_string)
@@ -446,6 +469,9 @@
     
     objects = XtdComment.objects
     
+    def __str__(self):
+        return self.id
+    
     class Meta:
         ordering = ["thread_id", "id"]
     
@@ -480,7 +506,9 @@
 class MetaCategoryInfo(models.Model):
     comment = models.ForeignKey('IconolabComment', on_delete=models.CASCADE)
     metacategory = models.ForeignKey('MetaCategory', on_delete=models.CASCADE)
-
+    
+    def __str__(self):
+        return "metacategory:"+self.metacategory.label+":on:"+self.comment.id
 
 class CommentAttachement(models.Model):
      
@@ -495,4 +523,11 @@
     
     comment = models.ForeignKey('IconolabComment', related_name='attachments', on_delete=models.CASCADE)
     attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=0)
-    data = models.TextField(blank=False)
\ No newline at end of file
+    data = models.TextField(blank=False)
+    
+class UserProfile(models.Model):
+    user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
+    administers_collection = models.ForeignKey('Collection', related_name='collection', blank=True, null=True)
+    
+    def __str__(self):
+        return "profile:"+self.user.username
\ No newline at end of file
--- a/src/iconolab/settings/__init__.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/settings/__init__.py	Thu Aug 18 10:58:55 2016 +0200
@@ -10,7 +10,7 @@
 https://docs.djangoproject.com/en/1.9/ref/settings/
 """
 
-import os
+import os, logging
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -46,7 +46,6 @@
 # Application definition
 
 INSTALLED_APPS = [
-    'restapi',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
@@ -82,6 +81,58 @@
 ]
 
 
+# Logging
+
+LOG_FILE = os.path.abspath(os.path.join(BASE_DIR,"../../run/log/log.txt"))
+LOG_LEVEL = logging.DEBUG
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': True,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse'
+        }
+    },
+    'formatters' : {
+        'simple' : {
+            'format': "%(asctime)s - %(levelname)s : %(message)s",
+        },
+        'semi-verbose': {
+            'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
+        },
+    },
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        },
+        'stream_to_console': {
+            'level': LOG_LEVEL,
+            'class': 'logging.StreamHandler'
+        },
+        'file': {
+            'level': LOG_LEVEL,
+            'class': 'logging.FileHandler',
+            'filename': LOG_FILE,
+            'formatter': 'semi-verbose',
+        },
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['file'],
+            'level': LOG_LEVEL,
+            'propagate': True,
+        },
+        'iconolab': {
+            'handlers': ['file'],
+            'level': LOG_LEVEL,
+            'propagate': True,
+        },
+    }
+}
+
+
 ROOT_URLCONF = 'iconolab.urls'
 
 TEMPLATES = [
--- a/src/iconolab/settings/dev.py.tmpl	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/settings/dev.py.tmpl	Thu Aug 18 10:58:55 2016 +0200
@@ -42,7 +42,6 @@
 # Application definition
 
 INSTALLED_APPS = [
-    'restapi',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
@@ -52,14 +51,14 @@
     'django.contrib.sites',
     'django_comments',
     'django_comments_xtd',
-    'notifications',
-    'reversion',
-    'reversion_compare',
+    'haystack',
     'iconolab.apps.IconolabApp',
     'sorl.thumbnail',
     'notifications'
 ]
 
+
+
 COMMENTS_APP = "django_comments_xtd"
 COMMENTS_XTD_MODEL = "iconolab.models.IconolabComment"
 COMMENTS_XTD_FORM_CLASS = 'iconolab.forms.comments.IconolabCommentForm'
@@ -118,6 +117,58 @@
     }
 }
 
+# Logging
+
+LOG_FILE = os.path.abspath(os.path.join(BASE_DIR,"../../run/log/log.txt"))
+LOG_LEVEL = logging.DEBUG
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': True,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse'
+        }
+    },
+    'formatters' : {
+        'simple' : {
+            'format': "%(asctime)s - %(levelname)s : %(message)s",
+        },
+        'semi-verbose': {
+            'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
+        },
+    },
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        },
+        'stream_to_console': {
+            'level': LOG_LEVEL,
+            'class': 'logging.StreamHandler'
+        },
+        'file': {
+            'level': LOG_LEVEL,
+            'class': 'logging.FileHandler',
+            'filename': LOG_FILE,
+            'formatter': 'semi-verbose',
+        },
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['file'],
+            'level': LOG_LEVEL,
+            'propagate': True,
+        },
+        'iconolab': {
+            'handlers': ['file'],
+            'level': LOG_LEVEL,
+            'propagate': True,
+        },
+    }
+}
+
+
 # Haystack connection
 HAYSTACK_CONNECTIONS = {
     'default': {
@@ -171,4 +222,26 @@
 
 USE_TZ = True
 
+
+IMPORT_FIELDS_DICT = {
+    "AUTR": [],
+    "ECOLE": [],
+    "TITR": ["Titre"],
+    "DENO": [],
+    "APPL": [],
+    "PERI": ["Période"],
+    "MILL": [],
+    "EPOCH": [],
+    "TECH": [],
+    "DIMS": ["Dimensions"],
+    "EPOCH": [],
+    "LIEUX": [],
+    "DECV": [],
+    "LOCA": ["Localisation"],
+    "PHOT": ["Photo"],
+    "INV": ["No inventaire",],
+    "REF": ["REFERENCE"],
+}
+
 INTERNAL_TAGS_URL = BASE_URL
+JOCONDE_NOTICE_BASE_URL = "http://www.culture.gouv.fr/public/mistral/joconde_fr?ACTION=CHERCHER&FIELD_98=REF&VALUE_98="
--- a/src/iconolab/signals/handlers.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/signals/handlers.py	Thu Aug 18 10:58:55 2016 +0200
@@ -12,22 +12,23 @@
 def increment_stats_on_new_revision(sender, instance, **kwargs):
     from iconolab.models import AnnotationRevision
     if sender == AnnotationRevision:
-        # Annotation stats
-        annotation = instance.annotation
-        annotation.stats.submitted_revisions_count += 1
-        if instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]:
-            annotation.stats.accepted_revisions_count += 1
-        if instance.state == AnnotationRevision.ACCEPTED and instance.merge_parent_revision is not None and instance.merge_parent_revision.state == AnnotationRevision.STUDIED:
-            annotation.stats.awaiting_revisions_count -= 1
-        if instance.state in [AnnotationRevision.AWAITING]:
-            annotation.stats.awaiting_revisions_count += 1
-        annotation.stats.set_tags_stats()
-        annotation.stats.save()
-        # Image stats
-        image = instance.annotation.image
-        image.stats.submitted_revisions_count += 1
-        image.stats.set_tags_stats()
-        image.stats.save()
+        if instance.parent_revision:
+            # Annotation stats
+            annotation = instance.annotation
+            annotation.stats.submitted_revisions_count += 1
+            if instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]:
+                annotation.stats.accepted_revisions_count += 1
+            if instance.state == AnnotationRevision.ACCEPTED and instance.merge_parent_revision is not None and instance.merge_parent_revision.state == AnnotationRevision.STUDIED:
+                annotation.stats.awaiting_revisions_count -= 1
+            if instance.state in [AnnotationRevision.AWAITING]:
+                annotation.stats.awaiting_revisions_count += 1
+            annotation.stats.set_tags_stats()
+            annotation.stats.save()
+            # Image stats
+            image = instance.annotation.image
+            image.stats.submitted_revisions_count += 1
+            image.stats.set_tags_stats()
+            image.stats.save()
         
     
 def increment_stats_on_new_comment(sender, instance, created, **kwargs):
@@ -85,12 +86,12 @@
             
 
 def notify_users_on_metacategory(sender, instance, created, **kwargs):
-    from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation
+    from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation, UserProfile
     if sender == MetaCategoryInfo and created:
         related_metacategory = instance.metacategory
         related_comment = instance.comment
         if related_comment.content_type.app_label == "iconolab" and related_comment.content_type.model == "annotation":
-            comment_annotation = Annotation.objects.get(id=related_comment.object_pk)
+            comment_annotation = Annotation.objects.prefetch_related("image__item__collection").get(id=related_comment.object_pk)
             if related_metacategory.triggers_notifications == MetaCategory.COMMENTERS:
                 for commenter in comment_annotation.stats.commenters.exclude(id=related_comment.user.id).all():
                     notify.send(related_comment.user, recipient=commenter, verb='a fait un appel à contribution', action_object=related_comment, target=comment_annotation)
@@ -98,7 +99,8 @@
                 for contributor in comment_annotation.stats.contributors.exclude(id=related_comment.user.id).all():
                     notify.send(related_comment.user, recipient=contributor, verb='a fait un appel à contribution', action_object=related_comment, target=comment_annotation)
             if related_metacategory.triggers_notifications == MetaCategory.COLLECTION_ADMINS:
-                pass
+                for collection_admin in UserProfile.objects.filter(administers_collection=comment_annotation.image.item.collection).all():
+                    notify.send(related_comment.user, recipient=collection_admin.user, verb='a fait un appel à expertise', action_object=related_comment, target=comment_annotation)
         
 def notify_users_on_new_revision(sender, instance, **kwargs):
     from iconolab.models import AnnotationRevision
@@ -114,6 +116,15 @@
             notify.send(instance.annotation.author, recipient=instance.author, verb='a étudié votre révision', action_object=instance, target=instance.annotation)
         
 
+def create_user_profile(sender, instance, created, **kwargs):
+    from iconolab.models import UserProfile
+    from django.contrib.auth.models import User
+    if sender == User and created:
+        UserProfile.objects.create(user=instance)
+
+# User profile connect
+post_save.connect(create_user_profile)
+
 # Stats handlers connect
 post_save.connect(increment_annotations_count)
 post_save.connect(increment_stats_on_new_comment)
--- a/src/iconolab/static/iconolab/css/iconolab.css	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/static/iconolab/css/iconolab.css	Thu Aug 18 10:58:55 2016 +0200
@@ -70,6 +70,8 @@
 	cursor: pointer;	
 }
 
+/* BADGES */
+
 .badge-error {
   background-color: #b94a48;
 }
@@ -89,6 +91,30 @@
 .notif-badge{
   margin-bottom: 5px;
 }
+
+/* USER PAGE */
+
 .show-all-notifications{
   cursor: pointer;
+}
+.annotation-panel{
+	min-width: 535px;
+}
+.annotation-detail{
+	vertical-align: middle;
+	display:inline-block;
+	margin-right: 15px;
+	margin-left: 15px;
+}
+.no-user-annotation{
+	margin-left: 15px;
+}
+.dt-annotation{
+	margin-bottom: 10px;
+}
+.userpage-annotation-btn{
+	display:inline-block; 
+	vertical-align: top; 
+	margin-top: 15px; 
+	margin-right: 15px;
 }
\ No newline at end of file
Binary file src/iconolab/static/iconolab/img/glossary_example.png has changed
Binary file src/iconolab/static/iconolab/img/logo_IRI_footer.png has changed
Binary file src/iconolab/static/iconolab/img/logo_mcc_footer.png has changed
--- a/src/iconolab/templates/iconolab/detail_annotation.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_annotation.html	Thu Aug 18 10:58:55 2016 +0200
@@ -27,7 +27,7 @@
 					{% endthumbnail %}
 				</div>
         	<p @click="toggleZoomView" style="padding-top:2px"><i class="fa fa-search-plus showPointer"></i></p>
-            <a class="btn btn-default btn-sm" href="{% url 'item_detail' collection_name image.item.item_guid %}"><i class="fa fa-eye" aria-hidden="true"></i> Revoir l'objet</a>
+            <a class="btn btn-default btn-sm" href="{% url 'item_detail' collection_name image.item.item_guid %}"><i class="fa fa-eye" aria-hidden="true"></i> Voir l'objet de cette annotation</a>
             <a class="btn btn-default btn-sm" href="{% url 'image_detail' collection_name image_guid %}"><i class="fa fa-picture-o" aria-hidden="true"></i> Voir les annotations sur l'image</a>
       
             </div>
@@ -219,7 +219,7 @@
             </div>
           </fieldset>
           <p class="submit">
-            <input class="btn btn-default" type="submit" name="post" class="submit-post" value="{% trans "Post" %}"/>
+            <input class="btn btn-default" type="submit" name="post" class="submit-post" value="Publier"/>
           </p>
         </form>
         {% endif %}
--- a/src/iconolab/templates/iconolab/detail_image.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_image.html	Thu Aug 18 10:58:55 2016 +0200
@@ -10,7 +10,7 @@
 <div class="row">
 	<div class="col-md-10 col-md-offset-1 text-center">
           <a class="btn btn-default btn-sm" href="{% url 'collection_home' collection_name %}"><i class="fa fa-list"></i> Retour à la liste d'objets</a>
-          <a class="btn btn-default btn-sm" href="{% url 'item_detail' collection_name image.item.item_guid %}"><i class="fa fa-eye" aria-hidden="true"></i> Revoir l'objet de cette image</a>
+          <a class="btn btn-default btn-sm" href="{% url 'item_detail' collection_name image.item.item_guid %}"><i class="fa fa-eye" aria-hidden="true"></i> Voir l'objet de cette image</a>
   		  <a class="btn btn-default btn-sm" href="{% url 'annotation_create' collection_name image_guid %}"><i class="fa fa-plus"></i> Annoter l'image</a>
         <br><br>
     
--- a/src/iconolab/templates/iconolab/detail_item.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_item.html	Thu Aug 18 10:58:55 2016 +0200
@@ -31,7 +31,7 @@
         {% if item.metadatas.discovery_context %}<h5>Contexte de découverte: <small>{{item.metadatas.discovery_context}}</small></h5>{% endif %}
         {% if item.metadatas.photo_credits %}<h5>Crédits photographique: <small>{{item.metadatas.photo_credits}}</small></h5>{% endif %}
         {% if item.metadatas.inventory_number %}<h5>Numéro d'inventaire: <small>{{item.metadatas.inventory_number}}</small></h5>{% endif %}
-        {% if item.metadatas.joconde_ref %}<h5>Cet objet dans Joconde <small>{{item.metadatas.get_joconde_url}}</small></h5>{% endif %}
+        {% if item.metadatas.joconde_ref %}<h5><a href="{{item.metadatas.get_joconde_url}}">Cet objet dans Joconde</a></h5>{% endif %}
         <br>
         {% if item.images.all.count > 1 %}
           <h4>Autres images pour cet objet: </h4>
--- a/src/iconolab/templates/iconolab/detail_revision.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_revision.html	Thu Aug 18 10:58:55 2016 +0200
@@ -25,7 +25,7 @@
 					{% endthumbnail %}
 				</div>
                 <br>
-                <a href="{% url 'image_detail' collection_name image_guid %}"><i class="fa fa-picture-o" aria-hidden="true"></i> Revoir l'image </a><br>
+                <a href="{% url 'item_detail' collection_name image.item.item_guid %}"><i class="fa fa-picture-o" aria-hidden="true"></i> Voir l'objet de cette annotation </a><br>
                 <a href="{% url 'annotation_detail' collection_name image_guid annotation_guid  %}"><i class="fa fa-reply" aria-hidden="true"></i> Retour sur l'annotation</a><br>
             </div>
 			<div id="revision-detail" class='col-xs-6' style="">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/glossary.html	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,71 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}
+  <div class="row" style="border: 1px solid gray;padding: 15px;">
+    <div class="col-md-9">
+    <h1>Le projet</h1>
+    <p class="text-justify">Iconolab est une plateforme contributive grâce à laquelle les utilisateurs peuvent explorer et annoter des collections (fond) d'images fournis par plusieurs musées et référencés dans la base de données Joconde. 
+    <br><br>
+    Sur cette plateforme, il est possible de découper des fragments d'une image et de lui assigner des mots-clés pour en faire une annotation. Les autres utilisateurs de la plateforme peuvent ensuite échanger, débattre et faire évoluer ces annotations. 
+    <br><br>
+    Cette page décrit le vocabulaire employé sur la plateforme afin de vous aider à en comprendre le fonctionnement.
+    </p>
+    </div>
+    <div class="col-md-3 text-center">
+         <img src="{% static 'iconolab/img/glossary_example.png' %}" width="200" height="200">
+         <br><h4 class="text-center"><small>Exemple de fragment</small></h6>
+    </div>
+    <div class="col-md-12">
+    <h2>Glossaire <small>Iconolab</small></h2>
+      <div class="row">
+        <div class="col-md-6">
+          <dl class="dl-horizontal">
+            <dt>Fond</dt>
+            <dd class="text-justify">Un "fond" dans Iconolab représente une collection d'objets, chacun présentant une ou plusieurs images sur laquelle les utilisateurs vont pouvoir créer des annotations et commenter sur les annotations</dd>
+            <br>
+            
+            <dt>Image</dt>           
+            <dd class="text-justify">Une "image" est liée à un objet. C'est l'élément sur lequel les utilisateurs vont pouvoir créer des annotations.</dd>
+            <br>
+            
+            <dt>Révision</dt>
+            <dd class="text-justify">Une "révision" est un état de l'annotation à un instant T. Une révision est créée à chaque fois qu'un utilisateur édite une annotation. <br><br> Les utilisateurs peuvent également proposer des révisions sur les annotations des autres utilisateurs. <br><br> La révision acceptée la plus récente est dite "Révision courante" et représente l'état officiel/publié de l'annotation.</dd>
+            <br>
+            <dt>Pertinence et fiabilité <br>des mot-clés</dt>
+            <dd class="text-justify">
+              Chaque mot-clé est accompagné de deux notes qui représentent la pertinence et la fiabilité de celui-ci. C'est à l'utilisateur qui pose les mot-clés d'estimer ces deux notions.<br><br>
+              <ul>
+              <li><b>La pertinence:</b> Indique le niveau de précision du mot-clé, càd si le mot-clé est plus ou moins généraliste dans le contexte où il est posé (Par exemple: Napoléon 3 est un mot-clé plus pertinent que Napoléon)</li>
+              <li><b>La fiabilité:</b> Indique à quel point l'utilisateur est certain que le mot-clé correspond à l'annotation sur laquelle il est posée (par exemple, si un utilisateur pose le mot-clé "église" sur un bâtiment en ruine mais n'est pas certain que ça soit bel et bien une église, il se notera une faible fiabilité)</li>
+              </ul>
+            </dd>
+          </dl>
+        </div>
+        <div class="col-md-6">
+          <dl class="dl-horizontal">
+            <dt>Objet</dt>
+            <dd class="text-justify">Un "objet" représente le sujet d'une notice Joconde, dans le contexte d'un fonds Iconolab. Il lui est associé une partie des métadonnées de la notice Joconde correspondante (à des fins d'affichage), et une ou plusieurs images.</dd>
+            <br>
+            <dt>Annotation</dt>
+            <dd class="text-justify">
+            Une "annotation" est créée par un utilisateur sur une image. Chaque annotation regroupe les éléments suivants:<br><br>
+              <ul>
+              <li><b>Titre</b></li>
+              <li><b>Description</b></li>
+              <li><b>Mot-clés:</b> un mot-clé est un mot-clé qui sera associé à l'annotation. En plus de cela, chaque mot-clé doit être associé d'une évaluation de "pertinence" (voir plus bas) et de "fiabilité" (voir plus bas)</li>
+              <li><b>Fragments:</b> le fragment est la partie de l'image (découpée à l'aide d'un éditeur) sur laquelle porte l'annotation. S'il n'y a pas de fragment, l'annotation porte sur l'image entière.</li>
+              <li><b>Commentaires:</b> chaque Annotation ouvre un fil de commentaires sur lesquels les utilisateurs vont pouvoir intervenir. Quand on créé une annotation, on doit également indiquer dans un commentaire la raison pour laquelle on a créé cette annotation.</li>
+              </ul>
+            </dd>
+          </dl>
+        </div>
+      </div>
+    </div>
+  </div>
+{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/home.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/home.html	Thu Aug 18 10:58:55 2016 +0200
@@ -8,11 +8,45 @@
 {% block content %}
 
 {% for collection in collections.all %}
-<h1>Fond {{ collection.verbose_name }} <a href="{% url 'collection_home' collection.name %}"><span class="glyphicon glyphicon-link" aria-hidden="true"></span></a></h1>
+<div id="collection-panel-{{collection.name}}" class="container collection-container panel panel-default {% if forloop.first %}selected{% endif %}" style="padding-top: 15px; padding-bottom: 15px;">
+  <div class="row">
+    <div class="col-md-4">
+      {% thumbnail collection.image "350x350" crop=False as im %}
+        <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+      {% endthumbnail %}
+    </div>
+    <div class="col-md-8">
+      <h1 class="collection-title" style="margin-top:0px;"><small>Fond Iconolab</small> {{ collection.verbose_name }}</h1>
+      <p class="text-justify"> {{collection.description | safe}} </p>
+      <a href="{% url 'collection_home' collection.name %}" class="btn btn-default btn-sm">Contribuer au fond {{collection.verbose_name}}</a>
+    </div>
+  </div>
+</div>
 {% endfor %}
+<div class="collection-navbar text-center">
+  <h4>Les autres fonds dans Iconolab</h4>
+  <ul class="list-inline list-unstyled">
+    {% for collection in collections.all %}
+      <li><div id="show-collection-{{collection.name}}" class="btn btn-default btn-xs {% if forloop.first %}btn-primary{% endif %} btn-collection">{{collection.verbose_name}}</div></li>
+    {% endfor %}
+  </ul>
+</div>
+{% endblock %}
 
-{% get_current_language as LANGUAGE_CODE %}
-{% get_available_languages as LANGUAGES %}
-{% get_current_language_bidi as LANGUAGE_BIDI %}
-the current language is {{ LANGUAGE_CODE }}
+{% block footer_js %}
+<script>
+	$(".collection-container:not(.selected)").hide();
+	var selectedID = /collection\-panel\-([0-9a-z\-]+)/.exec($(".collection-container.selected").attr("id"))[1];
+	$(".btn-collection").on("click", function(e){
+	    selectedID = /show\-collection\-([0-9a-z\-]+)/.exec($(this).attr("id"))[1];
+		if (!$(this).hasClass("btn-primary")){
+		    $(".collection-container").removeClass("selected");
+		    $(".collection-container:not(.selected)").hide();
+		    $(".btn-collection").removeClass("btn-primary")
+		    $(this).addClass("btn-primary")
+		    $(".collection-container#collection-panel-"+selectedID).show()
+		    $(".collection-container#collection-panel-"+selectedID).addClass("selected")
+		}
+	})
+</script>
 {% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/user_home.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab/user_home.html	Thu Aug 18 10:58:55 2016 +0200
@@ -9,8 +9,8 @@
 {% load notifications_tags %}
 
 {% block content %}
-  <div id="user-profile-block" class="row" style="border: 1px solid gray;">
-    <div class="col-md-12">
+  <div class="row" style="border: 1px solid gray;">
+    <div class="col-md-12 user-profile-block" >
       
       <h3>{% if profile_user == request.user %}Mon espace:{% else %}Profil: {% endif %} {{profile_user.username}}</h3>
       <div class="panel panel-default" style="padding-left: 10px; padding-right: 10px;">
@@ -53,64 +53,77 @@
         <div class="row">
           <div class="col-md-6">
             <h4>{% if profile_user == request.user %}Mes annotations:{% else %}Annotations de {{profile_user.username}}{% endif %} </h4>
-            <ul class="list-inline">
+            
             {% if not user_annotations %}
-              Aucune annotation à afficher
+              <h4 class="no-user-annotation"><small>Aucune annotation à afficher</small></h4>
+            {% else %}
+              <ul class="list-inline">
+                {% for annotation in user_annotations.all %}
+                  {% include "partials/user_page_annotation_panel.html" with annotation=annotation %}
+                {% endfor %}
+              </ul>
             {% endif %}
-            {% for annotation in user_annotations.all %}
-            <li>
-              <div class="panel panel-default" style="min-width: 375px;">
-                  <div class="panel-heading">{{annotation.current_revision.title}}</div>
-                  <div class="fragment-container" style="position:relative; display:inline-block">
-                    {% thumbnail annotation.image.media "150x150" crop=False as im %}
-                      <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
-                        <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|clean_path }}" opacity="0.7" fill="orange"></path>
-                          </g>
-                        </svg>
-                      </a>
-                    {% endthumbnail %}
-                  </div>
-                  <div class="annotation-detail" style="display:inline-block; position:relative; margin-right: 15px;">
-                    <dl class="dl-horizontal">
-                      <dt>Commentaires: </dt>
-                      <dd><span class="badge">{{annotation.stats.comments_count}}</span></dd>
-                      <dt>Révisions en attente:</dt>
-                      <dd><span class="badge {% if annotation.stats.awaiting_revisions_count > 0 %}badge-warning{% endif %}">{{annotation.stats.awaiting_revisions_count}}</span></dd>
-                    </dl>
-                  </div>
-              </div>
-            </li>
-            {% endfor %}
-            </ul>
           </div>
           <div class="col-md-6">
             <h4>{% if profile_user == request.user %}Mes autres contributions:{% else %}Contributions de {{profile_user.username}}{% endif %} </h4>
             <dl>
-              <dt>
-              {% if profile_user == request.user %}
-                Annotations sur lesquelles j'ai commenté:
-              {% else %}
-                Annotations sur lesquelles {{profile_user.username}} a commenté:
-              {% endif %}
-              </dt>
-              <dd>{{user_comments_annotations.all}}</dd>
-              <dt>
+            <dt class="dt-annotation">
               {% if profile_user == request.user %}
                 Annotations sur lesquelles j'ai proposé des révisions
               {% else %}
                 Annotations sur lesquelles {{profile_user.username}} a proposé des révisions:
               {% endif %} 
               </dt>
-              <dd>{{user_revisions_annotations.all}}</dd>
+              <dd>
+              {% if not user_revisions_annotations %}
+                 <h4 class="no-user-annotation"><small>Aucune annotation à afficher</small></h4>
+              {% else %}
+                <ul class="list-inline">
+                  {% for annotation in user_revisions_annotations.all %}
+                    {% include "partials/user_page_annotation_panel.html" with annotation=annotation %}
+                  {% endfor %}
+                </ul>
+              {% endif %}
+              </dd>
+              <dt class="dt-annotation">
+              {% if profile_user == request.user %}
+                Annotations sur lesquelles j'ai commenté (sans proposer de révision):
+              {% else %}
+                Annotations sur lesquelles {{profile_user.username}} a commenté (sans proposer de révision):
+              {% endif %}
+              </dt>
+              <dd>
+              {% if not user_comments_annotations %}
+                 <h4 class="no-user-annotation"><small>Aucune annotation à afficher</small></h4>
+              {% else %}
+                <ul class="list-inline">
+                  {% for annotation in user_comments_annotations.all %}
+                    {% include "partials/user_page_annotation_panel.html" with annotation=annotation %}
+                  {% endfor %}
+                </ul>
+              {% endif %}
+              </dd>
             </dl>
           </div>
         </div>
       </div>
     </div>
+    {% if profile_user == request.user and profile_user.profile.administers_collection %}
+    <div class="col-md-12">
+      <div class="panel panel-default" style="padding: 10px;">
+      
+        <div class="alert alert-warning">
+          L'accès à ces fonctionnalités n'est pas encore disponible.
+        </div>
+        <h3><small>Administration Fond</small> {{user.profile.administers_collection.verbose_name}}</h3>
+        <div class="text-center">
+          <a href="#" class="btn btn-default disabled text-center">Tableau de bord</a>
+        </div>
+      </div>
+    </div>
+    {% endif %}
   </div>
+
 {% endblock %}
 
 {% block footer_js %}
--- a/src/iconolab/templates/iconolab_base.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/iconolab_base.html	Thu Aug 18 10:58:55 2016 +0200
@@ -1,6 +1,7 @@
 {% load staticfiles %}
 
 {% load notifications_tags %}
+{% load iconolab_tags %}
 
 <!DOCTYPE html>
 <html>
@@ -37,4 +38,17 @@
 
 	    {% endblock %}
 	</body>
+    <footer>
+      <div class="container">
+      <hr>
+      <div class="pull-left" style="margin-left:15px;">
+        <h4><small>Version {% version %}</small></h4>
+      </div>
+      <div class="pull-right partners-icons">
+        <a href="http://www.iri.centrepompidou.fr"><img src="{% static 'iconolab/img/logo_IRI_footer.png' %}"></a>
+        &nbsp;&nbsp;
+        <a href="http://www.culturecommunication.gouv.fr/"><img src="{% static 'iconolab/img/logo_mcc_footer.png' %}"></a>
+      </div>
+      </div>
+    </footer>
 </html>
\ No newline at end of file
--- a/src/iconolab/templates/partials/header.html	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templates/partials/header.html	Thu Aug 18 10:58:55 2016 +0200
@@ -12,7 +12,7 @@
     </div>
     <div id="navbar" class="navbar-collapse collapse">
       <ul class="nav navbar-nav">
-        <li><a href="#">Le projet</a></li>
+        <li><a href="{% url 'iconolab_help' %}">Le projet</a></li>
         {% if collection_name %}<li><a href="{% url 'collection_home' collection_name %}">Contribuer</a></li>{% endif %}
       </ul>
       
@@ -22,7 +22,7 @@
         {% if request.user.is_authenticated %}
           {% notifications_unread as unread_count %}
           <li><a href="{% url 'user_home' request.user.id %}">
-            {{user.username}}: Mon espace <span class="badge {% if unread_count %}badge-warning{% endif %}">
+            {{request.user.username}}: Mon espace <span class="badge {% if unread_count %}badge-warning{% endif %}">
             {{ unread_count }} <i class="fa fa-envelope-o" aria-hidden="true"></i> </span>
           </a></li>
           <li><a href="{% url 'account:logout' %}">Se déconnecter</a></li>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/partials/user_page_annotation_panel.html	Thu Aug 18 10:58:55 2016 +0200
@@ -0,0 +1,31 @@
+{% load thumbnail %}
+{% load iconolab_tags %}
+<li>
+  <div class="panel panel-default annotation-panel">
+      <div class="panel-heading">{{annotation.current_revision.title}}</div>
+      
+      <div class="fragment-container" style="position:relative; display:inline-block">
+        {% thumbnail annotation.image.media "150x150" crop=False as im %}
+          <a href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">
+            <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|clean_path }}" opacity="0.7" fill="orange"></path>
+              </g>
+            </svg>
+          </a>
+        {% endthumbnail %}
+      </div>
+      <div class="annotation-detail">
+        <dl class="dl-horizontal">
+          <dt>Commentaires: </dt>
+          <dd><span class="badge">{{annotation.stats.comments_count}}</span></dd>
+          <dt>Révisions en attente:</dt>
+          <dd><span class="badge {% if annotation.stats.awaiting_revisions_count > 0 %}badge-warning{% endif %}">{{annotation.stats.awaiting_revisions_count}}</span></dd>
+          <dt>Révisions acceptée:</dt>
+          <dd><span class="badge">{{annotation.stats.accepted_revisions_count}}</span></dd>
+        </dl>
+      </div>
+      <a class="btn btn-default btn-sm userpage-annotation-btn pull-right" href="{% url 'annotation_detail' annotation.image.item.collection.name annotation.image.image_guid annotation.annotation_guid %}">Voir annotation</a>
+  </div>
+</li>
\ No newline at end of file
--- a/src/iconolab/templatetags/iconolab_tags.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/templatetags/iconolab_tags.py	Thu Aug 18 10:58:55 2016 +0200
@@ -1,5 +1,6 @@
 from django.template import Library
 import sys
+from iconolab import __version__
 
 register = Library()
 
@@ -38,4 +39,8 @@
 		if len(path_infos) > 1 :
 			result = path_infos[1]
 
-	return result
\ No newline at end of file
+	return result
+
+@register.simple_tag
+def version():
+	return __version__
\ No newline at end of file
--- a/src/iconolab/urls.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/urls.py	Thu Aug 18 10:58:55 2016 +0200
@@ -45,7 +45,7 @@
     url(r'^user/(?P<slug>[a-z0-9\-]+)/home/?$', views.iconolab.UserHomeView.as_view(), name="user_home"),
     url(r'^user/notifications/all/?$', login_required(views.iconolab.UserNotificationsView.as_view()), name="user_notifications"),
     url(r'^errors/404', views.iconolab.NotFoundErrorView.as_view(), name="404error"),
-    url(r'^rest', include('restapi.urls')),
+    url(r'^help/', views.iconolab.HelpView.as_view(), name="iconolab_help"),
     url(r'^account/', include('iconolab.auth.urls', namespace='account')),
     url(r'^comments/', include('django_comments_xtd.urls')),
     url(r'^comments/annotation/post', views.comments.post_comment_iconolab, name="post_comment"),
--- a/src/iconolab/views/iconolab.py	Thu Aug 18 10:49:18 2016 +0200
+++ b/src/iconolab/views/iconolab.py	Thu Aug 18 10:58:55 2016 +0200
@@ -13,7 +13,9 @@
 from notifications.models import Notification
 from iconolab.models import Annotation, AnnotationRevision, Collection, Item, Image, IconolabComment, MetaCategory, MetaCategoryInfo
 from iconolab.forms.annotations import AnnotationRevisionForm
+import logging
 
+logger = logging.getLogger(__name__)
 
 class GlobalHomepageView(View):
     def get(self, request, *args, **kwargs):
@@ -51,7 +53,13 @@
         ).distinct()
         comments_annotations_str_id = IconolabComment.objects.filter(user=profile_user, content_type__app_label="iconolab", content_type__model="annotation").values_list("object_pk", flat=True)
         comments_annotations_id = [int(str_id) for str_id in comments_annotations_str_id]
-        context["user_comments_annotations"] = Annotation.objects.filter(id__in=comments_annotations_id).exclude(author=profile_user).distinct()
+        context["user_comments_annotations"] = Annotation.objects.filter(id__in=comments_annotations_id).exclude(author=profile_user).exclude(annotation_guid__in=context["user_revisions_annotations"].values_list("annotation_guid", flat=True)).prefetch_related(
+            "current_revision", 
+            "revisions", 
+            "image", 
+            "image__item", 
+            "image__item__collection"
+        ).distinct()
         if request.user.is_authenticated() and self.object == request.user:
             if request.GET.get("clear_notifications", False):
                 Notification.objects.filter(recipient=request.user).mark_all_as_read()
@@ -75,19 +83,46 @@
         context["notifications"] = notifications_list
         return render(request, 'iconolab/user_notifications.html', context)
 
-
-class CollectionHomepageView(View, ContextMixin):
+# Class with check_kwargs method to fetch objects from database depending on what level in the app we're currently at
+class IconolabObjectView(object):
     def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection)
-    
+        """
+            Returns a boolean depending on wether (True) or not (False) the objects were found and a tuple containing the objects, with a select_related/prefetch_related on relevant related objects
+            following this ordering: (collection, item, image, annotation, revision)
+        """
+        objects_tuple = ()
+        if "collection_name" in kwargs.keys():
+            try:
+                objects_tuple += (Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name')),)
+            except (ValueError, Collection.DoesNotExist):
+                return False, RedirectView.as_view(url=reverse('404error'))
+        if "item_guid" in kwargs.keys():
+            try:
+                objects_tuple += (Item.objects.prefetch_related("images").get(item_guid=kwargs.get('item_guid')),)
+            except (ValueError, Item.DoesNotExist):
+                return False, RedirectView.as_view(url=reverse('404error'))
+        if "image_guid" in kwargs.keys():
+            try:
+                objects_tuple += (Image.objects.prefetch_related("annotations", "item").get(image_guid=kwargs.get('image_guid')),)
+            except (ValueError, Image.DoesNotExist):
+                return False, RedirectView.as_view(url=reverse('404error'))
+        if "annotation_guid" in kwargs.keys():
+            try:
+                objects_tuple += (Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid')),)
+            except (ValueError, Annotation.DoesNotExist):
+                return False, RedirectView.as_view(url=reverse('404error'))
+        if "revision_guid" in kwargs.keys():
+            try:
+                objects_tuple += (AnnotationRevision.objects.select_related('parent_revision').get(revision_guid=kwargs.get('revision_guid')),)
+            except (ValueError, AnnotationRevision.DoesNotExist):
+                return False, RedirectView.as_view(url=reverse('404error'))
+        return True, objects_tuple
+
+class CollectionHomepageView(View, ContextMixin, IconolabObjectView):
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection) = result
+            (collection,) = result
         else:
             return result(request)
         context = super(CollectionHomepageView, self).get_context_data(**kwargs)
@@ -97,19 +132,7 @@
     
 
 
-class ShowItemView(View, ContextMixin):
-    
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            item = Item.objects.prefetch_related("images").get(item_guid=kwargs.get('item_guid'))
-        except (ValueError, Item.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, item)
-    
+class ShowItemView(View, ContextMixin, IconolabObjectView):
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
@@ -126,19 +149,7 @@
             image.stats.save()
         return render(request, 'iconolab/detail_item.html', context);
 
-class ShowImageView(View, ContextMixin):
-    
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image)
-    
+class ShowImageView(View, ContextMixin, IconolabObjectView):
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
@@ -152,7 +163,7 @@
         context['image'] = image
         return render(request, 'iconolab/detail_image.html', context)
     
-class CreateAnnotationView(View, ContextMixin):
+class CreateAnnotationView(View, ContextMixin, IconolabObjectView):
     
     def get_context_data(self, **kwargs):
         context = super(CreateAnnotationView, self).get_context_data(**kwargs)
@@ -160,21 +171,10 @@
         context['image_guid'] = self.kwargs.get('image_guid', '')
         return context
     
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image)
-    
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection, image) = result
+            (collection, image,) = result
         else:
             return result(request)
         annotation_form = AnnotationRevisionForm()
@@ -218,7 +218,7 @@
         context['tags_data'] = '[]'
         render(request, 'iconolab/change_annotation.html', context)
 
-class ShowAnnotationView(View, ContextMixin):
+class ShowAnnotationView(View, ContextMixin, IconolabObjectView):
     
     def get_context_data(self, **kwargs):
         context = super(ShowAnnotationView, self).get_context_data(**kwargs)
@@ -226,26 +226,11 @@
         context['image_guid'] = self.kwargs.get('image_guid', '')
         context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
         return context
-    
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            annotation = Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid'))
-        except (ValueError, Annotation.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image, annotation)
-    
+
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection, image, annotation) = result
+            (collection, image, annotation,) = result
         else:
             return result(request)
         context = self.get_context_data(**kwargs)
@@ -288,7 +273,7 @@
         return render(request, 'iconolab/detail_annotation.html', context)
 
 
-class EditAnnotationView(View, ContextMixin):
+class EditAnnotationView(View, ContextMixin, IconolabObjectView):
     
     def get_context_data(self, **kwargs):
         context = super(EditAnnotationView, self).get_context_data(**kwargs)
@@ -297,25 +282,10 @@
         context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
         return context
     
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            annotation = Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid'))
-        except (ValueError, Annotation.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image, annotation)
-    
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection, image, annotation) = result
+            (collection, image, annotation,) = result
         else:
             return result(request)
         annotation_form = AnnotationRevisionForm(instance=annotation.current_revision)
@@ -363,7 +333,7 @@
         return render(request, 'iconolab/change_annotation.html', context)
 
 
-class ShowRevisionView(View, ContextMixin):
+class ShowRevisionView(View, ContextMixin, IconolabObjectView):
     
     def get_context_data(self, **kwargs):
         context = super(ShowRevisionView, self).get_context_data(**kwargs)
@@ -372,30 +342,11 @@
         context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
         context['revision_guid'] = self.kwargs.get('revision_guid', '')
         return context
-    
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            annotation = Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid'))
-        except (ValueError, Annotation.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            revision = AnnotationRevision.objects.select_related('parent_revision').get(revision_guid=kwargs.get('revision_guid'))
-        except (ValueError, AnnotationRevision.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image, annotation, revision)
-    
+ 
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection, image, annotation, revision) = result
+            (collection, image, annotation, revision,) = result
         else:
             return result(request)
         context = self.get_context_data(**kwargs)
@@ -406,7 +357,7 @@
         context['tags_data'] = revision.get_tags_json()
         context['comment'] = revision.creation_comment.first()
         if request.user.is_authenticated() and annotation.author == request.user:
-            notified_revision = Notification.objects.filter(
+            ann_author_notified = Notification.objects.filter(
                     recipient=request.user, 
                     action_object_content_type__app_label="iconolab", 
                     action_object_content_type__model="annotationrevision",
@@ -415,13 +366,26 @@
                     target_content_type__model="annotation",
                     target_object_id=annotation.id
                 ).unread()
-            if notified_revision:
-                notified_revision.first().mark_as_read()
+            if ann_author_notified:
+                ann_author_notified.first().mark_as_read()
+                context["notified_revision"] = True
+        if request.user.is_authenticated() and revision.author == request.user:
+            rev_author_notified = Notification.objects.filter(
+                    recipient=request.user, 
+                    action_object_content_type__app_label="iconolab", 
+                    action_object_content_type__model="annotationrevision",
+                    action_object_object_id=revision.id,
+                    target_content_type__app_label="iconolab",
+                    target_content_type__model="annotation",
+                    target_object_id=annotation.id
+                ).unread()
+            if rev_author_notified:
+                rev_author_notified.first().mark_as_read()
                 context["notified_revision"] = True
         return render(request, 'iconolab/detail_revision.html', context)
 
         
-class MergeProposalView(View, ContextMixin):
+class MergeProposalView(View, ContextMixin, IconolabObjectView):
     
     def get_context_data(self, **kwargs):
         context = super(MergeProposalView, self).get_context_data(**kwargs)
@@ -430,30 +394,11 @@
         context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
         context['revision_guid'] = self.kwargs.get('revision_guid', '')
         return context
-    
-    def check_kwargs(self, kwargs):
-        try:
-            collection = Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name'))
-        except (ValueError, Collection.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            image = Image.objects.prefetch_related("annotations").get(image_guid=kwargs.get('image_guid'))
-        except (ValueError, Image.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            annotation = Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid'))
-        except (ValueError, Annotation.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        try:
-            revision = AnnotationRevision.objects.select_related('parent_revision').get(revision_guid=kwargs.get('revision_guid'))
-        except (ValueError, AnnotationRevision.DoesNotExist):
-            return False, RedirectView.as_view(url=reverse('404error'))
-        return True, (collection, image, annotation, revision)
-    
+
     def get(self, request, *args, **kwargs):
         success, result = self.check_kwargs(kwargs)
         if success:
-            (collection, image, annotation, revision) = result
+            (collection, image, annotation, revision,) = result
         else:
             return result(request)
         # Only show merge form if there is a revision to merge AND the current user is the annotation author
@@ -565,4 +510,7 @@
     
     
 class NotFoundErrorView(TemplateView):
-    template_name="errors/404error.html"
\ No newline at end of file
+    template_name="errors/404error.html"
+    
+class HelpView(TemplateView):
+    template_name="iconolab/glossary.html"
\ No newline at end of file
--- a/src/restapi/apps.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class RestapiConfig(AppConfig):
-    name = 'restapi'
--- a/src/restapi/models.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-from django.db import models
--- a/src/restapi/serializers.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-from rest_framework import serializers
-from iconolab.models import Annotation
-
-
-class AnnotationSerializer(serializers.ModelSerializer):
-	tags = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='get')
-	class Meta:
-		model = Annotation
-		fields = ('id', 'title', 'description', 'fragment', 'tags')
-		
-	def create(self, validated_data):
-		"""
-		Create a new Annotation
-		"""
-		return Annotation.create(**validated_data)
-
-	def update(self, instance, validated_data):
-		instance.title = validated_data.get('title', instance.title)
-		instance.description = validated_data.get('description', instance.description)
-		instance.fragment = validated_data.get('fragment', instance.fragment)
-		instance.save()
-		return instance
\ No newline at end of file
--- a/src/restapi/tests.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
--- a/src/restapi/urls.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-from django.conf.urls import url
-from . import views
-
-urlpatterns = [
-	url(r'^$', views.index, name='index'),
-	url(r'annotation$', views.annotation_list),
-	url(r'annotation/(?P<pk>[0-9]+)/$', views.get)
-]
\ No newline at end of file
--- a/src/restapi/views.py	Thu Aug 18 10:49:18 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-from django.shortcuts import render, HttpResponse
-from pprint import pprint
-
-from django.views.decorators.csrf import csrf_exempt
-from rest_framework.renderers import JSONRenderer
-from rest_framework.parsers import JSONParser
-from iconolab.models import Annotation
-from .serializers import AnnotationSerializer
-
-class JSONResponse(HttpResponse):
-	def __init__(self, data, **kwargs):
-		content = JSONRenderer().render(data)
-		kwargs['content_type'] = 'application/json'
-		super(JSONResponse, self).__init__(content)
-
-
-# Create your views here.
-
-def index(r):
-	return HttpResponse('<p>You better know ... </p>')
-
-@csrf_exempt
-def annotation_list(request):
-	
-	if request.method == 'GET':
-		annotations = Annotation.objects.all()
-		serializer = AnnotationSerializer(annotations, many=True)
-		return JSONResponse(serializer.data)
-
-
-
-def get(request, pk):
-	if request.method == 'GET':
-		
-		try:
-			annotation = Annotation.objects.get(pk=pk)
-		except Annotation.DoesNotExist:
-			return HttpResponse(status=404)
-
-		serializer = AnnotationSerializer(annotation) 
-		return JSONResponse(serializer.data)