Merged with image import
authorHarris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Fri, 12 Aug 2016 10:59:57 +0200
changeset 113 541dd2ecde0c
parent 112 201a73fdc0b5 (current diff)
parent 110 ce46bbafb079 (diff)
child 114 f0ce0058c09b
Merged with image import
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/management/commands/importimages.py	Fri Aug 12 10:59:57 2016 +0200
@@ -0,0 +1,187 @@
+# -*- coding: UTF-8 -*-
+from django.core.management.base import BaseCommand, CommandError
+from django.core.management import call_command
+from django.conf import settings
+from iconolab.models import Collection, Image, ImageStats, Item, ItemMetadata
+from PIL import Image as ImagePIL
+import os, csv, pprint, re, json
+
+class Command(BaseCommand):
+    help = "import images from a directory into the media folder and creates item and image objects"
+    
+    def add_arguments(self, parser):
+        parser.add_argument("csv_path")
+        parser.add_argument(
+            '--encoding',
+            dest='encoding',
+            default='utf-8',
+            help='CSV file encoding'
+        
+        )
+        parser.add_argument(
+            '--collection-fixture',
+            dest='collection_fixture',
+            default=False,
+            help='loads the fixture then insert extracted data into the created collection',
+        )
+        parser.add_argument(
+            '--collection-id',
+            dest='collection_id',
+            default=False,
+            help='insert extracted data into the specified collection instead of trying to load a collection fixture',
+        )
+    
+    def handle(self, *args, **options):
+        pp = pprint.PrettyPrinter(indent=4)
+        try:
+            # Check we have a collection to store data into:
+            source_dir = os.path.dirname(os.path.realpath(options.get("csv_path")))
+            print("# Checking collection args")
+            if options.get("collection_fixture"):
+                print("## Finding collection json data in "+source_dir)
+                fixture_path = os.path.join(source_dir, options.get("collection_fixture"))
+                if not os.path.isfile(fixture_path):
+                    print("### No "+options.get("collection_fixture")+".json file was found in the source directory")
+                    raise ValueError("!!! Fixture file "+fixture_path+" was not found !!!")
+                try:
+                    with open(fixture_path) as json_fixture_file:
+                        collection_data = json.loads(json_fixture_file.read())
+                        if len(collection_data) != 1:
+                            raise ValueError("!!! Collection fixture has either 0 or more than one item. It should only provide one and only one collection !!!")
+                        if collection_data[0]["model"] != "iconolab.Collection":
+                            raise ValueError("!!! Collection fixture should provide one iconolab.Collection object and nothing else. !!!")
+                except ValueError as e:
+                    raise ValueError("!!! JSON Data is invalid. !!!")    
+            elif options.get("collection_id"):
+                print("## Finding collection with id "+options.get("collection_id"))
+                try:
+                    collection = Collection.objects.get(pk=options.get("collection_id"))
+                except Collection.DoesNotExist:
+                    raise ValueError("!!! Collection with primary key "+options.get("collection_id")+" was not found, aborting !!!")
+            else:
+                raise ValueError("!!! No collection fixture or collection id, aborting because we can't properly generate data. !!!") 
+            # We read the csv
+            csvreader = csv.DictReader(open(options.get("csv_path"), encoding=options.get("encoding")), delimiter=";")
+            print("# Extracting data from csv file and storing it in standardized format")
+            # We store data using the Jocondelab keys, as defined in settings.IMPORT_FIELDS_DICT
+            cleaned_csv_data=[]
+            for row in csvreader:
+                cleaned_row_data = {}
+                for key in settings.IMPORT_FIELDS_DICT.keys():
+                    cleaned_row_data[key] = ""
+                    for row_key in row.keys():
+                        if row_key in settings.IMPORT_FIELDS_DICT[key]:
+                            cleaned_row_data[key] = row[row_key]
+                            break
+                cleaned_csv_data.append(cleaned_row_data)
+            
+            print("# Finding corresponding images and filtering csv data for found images")
+            # Listing image files in csv directory
+            image_list = [f for f in os.listdir(source_dir) if os.path.isfile(os.path.join(source_dir, f)) and not f.endswith(".csv")]
+            filtered_csv_data = []
+            # Now we trim the cleaned_csv_data dict to keep only entries that have at least one image
+            for item in cleaned_csv_data:
+                item["SRC_IMG_FILES"] = []
+                has_image = False
+                for image in image_list:
+                    if image.startswith(item["INV"]):
+                        item["SRC_IMG_FILES"].append(image)
+                        has_image = True
+                if has_image:
+                    filtered_csv_data.append(item)
+            print("## found " + str(len(filtered_csv_data))+" items with at least one image")
+            print("# Importing data into Iconolab")
+            if options.get("collection_fixture"):
+                print("## Loading collection fixture")
+                call_command("loaddata", fixture_path)
+                collection = Collection.objects.get(
+                    pk = collection_data[0]["pk"]
+                )
+            print("## Converting image and moving it to static dir, creating Image and Item objects")
+            target_dir = os.path.join(settings.MEDIA_ROOT, "uploads")
+            print("### Images will be stored in "+target_dir)
+            for item in filtered_csv_data:
+                print("#### Computing metadatas for item "+item["INV"]+" (inv number)")
+                item_authors = item["AUTR"]
+                item_school = item["ECOLE"]
+                item_designation = ""
+                if item.get("TITR", ""):
+                    item_designation = item["TITR"]
+                elif item.get("DENO", ""):
+                    item_designation = item["DENO"]
+                elif item.get("APPL", ""):
+                    item_designation = item["APPL"]
+                item_datation = ""
+                if item.get("PERI", ""):
+                    item_datation = item["PERI"]
+                elif item.get("MILL", ""):
+                    item_datation = item["MILL"]
+                elif item.get("EPOQ", ""):
+                    item_datation = item["EPOQ"]
+                item_technics = item["TECH"]
+                item_measurements = item["DIMS"]
+                item_create_or_usage_location = item["LIEUX"]
+                item_discovery_context = item["DECV"]
+                item_conservation_location = item["LOCA"]
+                item_photo_credits = item["PHOT"]
+                item_inventory_number = item["INV"]
+                item_joconde_ref = item["REF"]
+                if ItemMetadata.objects.filter(item__collection = collection, inventory_number = item_inventory_number).exists():
+                    print("#### An item with "+item["INV"]+" for inventory number, already exists in databse in the import collection")
+                else:
+                    print("#### Creating item "+item["INV"]+" (inv number) in databse")
+                    item_object = Item.objects.create(
+                        collection = collection
+                    )
+                    ItemMetadata.objects.create(
+                        item = item_object,
+                        authors = item_authors,
+                        school = item_school,
+                        designation = item_designation,
+                        datation = item_datation,
+                        technics = item_technics,
+                        measurements = item_measurements,
+                        create_or_usage_location = item_create_or_usage_location,
+                        discovery_context = item_discovery_context,
+                        conservation_location = item_conservation_location,
+                        photo_credits = item_photo_credits,
+                        inventory_number = item_inventory_number,
+                        joconde_ref = item_joconde_ref
+                    )
+                    print("#### Computing item image(s)")
+                    for image in item["SRC_IMG_FILES"]:
+                        (image_name, ext) = os.path.splitext(image)
+                        image_path = os.path.join(target_dir, image_name) + ".jpg"
+                        if os.path.isfile(image_path):
+                            print("##### A jpeg file already exists in target dir for "+ image)
+                            try:
+                                im = ImagePIL.open(image_path)
+                                im_width, im_height = im.size
+                            except Exception as e:
+                                print(e)
+                        else:
+                            jpeg_img_path = image_path
+                            try:
+                                im = ImagePIL.open(os.path.join(source_dir, image))
+                                print("##### Generating or copying jpeg for "+image)
+                                im.thumbnail(im.size)
+                                im.save(jpeg_img_path, "JPEG", quality=100)
+                                im_width, im_height = im.size
+                            except Exception as e:
+                                print(e)
+                        new_image = Image.objects.create(
+                            item = item_object,
+                            media = image_path,
+                            name = image_name,
+                            height = im_height,
+                            width = im_width
+                        )
+                        ImageStats.objects.create(
+                            image = new_image
+                        )
+            print("# All done!")
+        except FileNotFoundError:
+            print("!!! File "+options.get("csv_path")+" does not exist. !!!")
+        except ValueError as e:
+            print(str(e))
+ 
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0008_auto_20160811_1050.py	Fri Aug 12 10:59:57 2016 +0200
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-08-11 10:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('iconolab', '0007_auto_20160805_1304'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='itemmetadata',
+            name='description',
+        ),
+        migrations.RemoveField(
+            model_name='itemmetadata',
+            name='domain',
+        ),
+        migrations.RemoveField(
+            model_name='itemmetadata',
+            name='title',
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='authors',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='conservation_location',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='create_or_usage_location',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='datation',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='designation',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='discovery_context',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='inventory_number',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='measurements',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='photo_credits',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='school',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AddField(
+            model_name='itemmetadata',
+            name='technics',
+            field=models.CharField(default='', max_length=255),
+        ),
+        migrations.AlterField(
+            model_name='itemmetadata',
+            name='joconde_ref',
+            field=models.CharField(default='', max_length=255),
+        ),
+    ]
--- a/src/iconolab/models.py	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/models.py	Fri Aug 12 10:59:57 2016 +0200
@@ -43,10 +43,22 @@
     
 class ItemMetadata(models.Model):
     item = models.OneToOneField('Item', related_name='metadatas')
-    joconde_ref = models.CharField(max_length=20)
-    domain = models.CharField(max_length=255)
-    title = models.CharField(max_length=255)
-    description = models.CharField(max_length=255)
+    authors = models.CharField(max_length=255, default="")
+    school = models.CharField(max_length=255, default="")
+    designation = models.CharField(max_length=255, default="")
+    datation = models.CharField(max_length=255, default="")
+    technics = models.CharField(max_length=255, default="")
+    measurements = models.CharField(max_length=255, default="")
+    create_or_usage_location = models.CharField(max_length=255, default="")
+    discovery_context = models.CharField(max_length=255, default="")
+    conservation_location = models.CharField(max_length=255, default="")
+    photo_credits = models.CharField(max_length=255, default="")
+    inventory_number = models.CharField(max_length=255, default="")
+    joconde_ref = models.CharField(max_length=255, default="")
+    
+    @property
+    def get_joconde_url(self):
+        return self.joconde_ref
 
 
 class ImageStats(models.Model):
--- a/src/iconolab/settings/__init__.py	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/settings/__init__.py	Fri Aug 12 10:59:57 2016 +0200
@@ -147,3 +147,27 @@
 USE_L10N = True
 
 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"],
+
+}
+NO_IMG_CONVERSION_EXTS = [".jpg"]
+IMG_CONVERSION_EXTS = [".tif", ".tiff"]
--- a/src/iconolab/templates/iconolab/detail_annotation.html	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_annotation.html	Fri Aug 12 10:59:57 2016 +0200
@@ -77,7 +77,7 @@
           <h4 id="annotation-comments-header">Commentaires</h4>
           <ul class="list-group annotation-comments" id="comments">
             {% for comment in comments %}
-              <li class="list-group-item" id="c{{ comment.id }}" style="margin-left:calc({{ comment.level }}*5px);">
+              <li class="list-group-item {% if comment.id in notifications_comments_ids %}list-group-item-warning{% endif %}" id="c{{ comment.id }}" style="margin-left:calc({{ comment.level }}*5px);">
                 <p id="c{{comment.id}}-content" class="comment-content">{{ comment.comment }}</p>
                 <hr class="comment-separator">
                 {% if comment.allow_thread and user.is_authenticated %}<div class="pull-right"><a class="btn btn-default btn-xs reply-to-btn" id="reply-to-{{comment.id}}" class="comment-reply-link">Répondre</a></div>{% endif %}
--- a/src/iconolab/templates/iconolab/detail_item.html	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_item.html	Fri Aug 12 10:59:57 2016 +0200
@@ -21,11 +21,18 @@
         {% endfor %}    
     </div>
     <div class="col-md-6">
-        <h2>{% if item.metadatas.title %}{{item.metadatas.title}}{% else %}Objet sans titre{% endif %}</h2>
-        {% if item.metadatas.description %}
-          <h4>Description: </h4>
-          <p>{{item.metadatas.description}}</p><br>
-        {% endif %}
+        {% if item.metadatas.designation %}<h3>Désignation: <small>{{item.metadatas.designation}}</small></h3>{% endif %}
+        {% if item.metadatas.authors %}<h4>Auteur(s): <small>{{item.metadatas.designation}}</small></h4>{% endif %}
+        {% if item.metadatas.conservation_location %}<h4>Conservé à: <small>{{item.metadatas.conservation_location}}</small></h4>{% endif %}
+        {% if item.metadatas.datation %}<h4>Datation: <small>{{item.metadatas.datation}}</small></h4>{% endif %}
+        {% if item.metadatas.technics %}<h5>Techniques: <small>{{item.metadatas.technics}}</small></h5>{% endif %}
+        {% if item.metadatas.measurements %}<h5>Mesures: {{item.metadatas.measurements}}</h5>{% endif %}
+        {% if item.metadatas.create_or_usage_location %}<h5>Lieu de création/utilisation: <small>{{item.metadatas.create_or_usage_location}}</small></h5>{% endif %}
+        {% 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 %}
+        <br>
         {% if item.images.all.count > 1 %}
           <h4>Autres images pour cet objet: </h4>
           {% for image in item.images.all %}
--- a/src/iconolab/templates/iconolab/user_home.html	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/templates/iconolab/user_home.html	Fri Aug 12 10:59:57 2016 +0200
@@ -16,13 +16,13 @@
       <div class="panel panel-default" style="padding-left: 10px; padding-right: 10px;">
         {% if profile_user == request.user %}
         {% notifications_unread as unread_count %}
-        <h4><span class="badge notif-badge {% if unread_count %}badge-error{% endif %}">{{unread_count}}</span> Notifications non lues 
+        <h4><span class="badge notif-badge {% if unread_count %}badge-warning{% endif %}">{{unread_count}} <i class="fa fa-envelope-o" aria-hidden="true"></i></span> Notifications non lues 
           <a href="{% url 'user_notifications' %}" class="btn btn-default btn-xs">Voir toutes mes notifications</a>
-          <a href="{% url 'user_home' profile_user.id %}?clear_notifications=True" class="btn btn-default btn-xs">Tout marquer comme lu</a>
+          {% if unread_count %}<a href="{% url 'user_home' profile_user.id %}?clear_notifications=True" class="btn btn-default btn-xs">Tout marquer comme lu</a>{% endif %}
         </h4>
         <div class="row">
           <div class="col-md-12">
-            {% if notifications %}
+            {% if unread_count %}
               <ul class="list-group">
               {% for notification in notifications.unread %}
                 <a  
--- a/src/iconolab/templates/iconolab/user_notifications.html	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/templates/iconolab/user_notifications.html	Fri Aug 12 10:59:57 2016 +0200
@@ -33,6 +33,30 @@
               </a>
             {% endfor %}
             </ul>
+            
+            {% if notifications.has_previous or notifications.has_next %}
+            <ul class="pagination pull-right">
+              {% if notifications.has_previous %}
+              <li>
+                <a href="{% url 'user_notifications' %}?page={{notifications.previous_page_number}}" aria-label="Previous">
+                  <span aria-hidden="true">&laquo;</span>
+                </a>
+              </li>
+              {% endif %}
+              {% for page in notifications.paginator.page_range %}
+                <li id="page-link-{{page}}" class="pagination-link {% if page == notifications.number %}active{% endif %}">
+                  <a {% if page != notifications.number %}href="{% url 'user_notifications' %}?page={{page}}"{% endif %}>{{page}}</a>
+                </li>
+              {% endfor %}
+              {% if notifications.has_next %}
+              <li>
+                <a href="{% url 'user_notifications' %}?page={{notifications.next_page_number}}" aria-label="Next">
+                  <span aria-hidden="true">&raquo;</span>
+                </a>
+              </li>
+              {% endif %}
+            </ul>
+            {% endif %}
           {% endif %}
       </div>
     </div>
--- a/src/iconolab/templates/partials/header.html	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/templates/partials/header.html	Fri Aug 12 10:59:57 2016 +0200
@@ -22,7 +22,10 @@
       <ul class="nav navbar-nav navbar-right">
         {% 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-error{% endif %}">{{ unread_count }}</span></a></li>
+          <li><a href="{% url 'user_home' request.user.id %}">
+            {{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>
         {% else %}
           <li><a href="{% url 'account:register' %}">Créer un compte</a></li>
--- a/src/iconolab/urls.py	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/urls.py	Fri Aug 12 10:59:57 2016 +0200
@@ -29,14 +29,14 @@
     url(r'^$', views.iconolab.RedirectView.as_view(url=reverse_lazy("home"))),
     url(r'^admin/', admin.site.urls),
     url(r'^home$', views.iconolab.GlobalHomepageView.as_view(), name="home"),
-    url(r'^collections/(?P<collection_name>[a-z]+)$', views.iconolab.CollectionHomepageView.as_view(), name='collection_home'), # Home fond
-    url(r'^collections/(?P<collection_name>[a-z]+)/items/(?P<item_guid>[^/]+)$', views.iconolab.ShowItemView.as_view(), name='item_detail'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)$', views.iconolab.ShowImageView.as_view(), name='image_detail'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/create$', login_required(views.iconolab.CreateAnnotationView.as_view()), name='annotation_create'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.iconolab.ShowAnnotationView.as_view(), name='annotation_detail'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.iconolab.EditAnnotationView.as_view()), name='annotation_edit'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[^/]+)/detail', views.iconolab.ShowRevisionView.as_view(), name='revision_detail'),
-    url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[^/]+)/merge$', login_required(views.iconolab.MergeProposalView.as_view()), name='annotation_merge'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)$', views.iconolab.CollectionHomepageView.as_view(), name='collection_home'), # Home fond
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/items/(?P<item_guid>[^/]+)$', views.iconolab.ShowItemView.as_view(), name='item_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)$', views.iconolab.ShowImageView.as_view(), name='image_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/create$', login_required(views.iconolab.CreateAnnotationView.as_view()), name='annotation_create'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.iconolab.ShowAnnotationView.as_view(), name='annotation_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.iconolab.EditAnnotationView.as_view()), name='annotation_edit'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[^/]+)/detail', views.iconolab.ShowRevisionView.as_view(), name='revision_detail'),
+    url(r'^collections/(?P<collection_name>[a-z0-9\-]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[^/]+)/merge$', login_required(views.iconolab.MergeProposalView.as_view()), name='annotation_merge'),
     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"),
--- a/src/iconolab/views/iconolab.py	Fri Aug 12 10:59:26 2016 +0200
+++ b/src/iconolab/views/iconolab.py	Fri Aug 12 10:59:57 2016 +0200
@@ -64,8 +64,15 @@
         context = {}
         notifications = Notification.objects.filter(recipient=request.user)
         context["notifications_unread_ids"] = notifications.unread().values_list("id", flat=True)
-        notifications.mark_all_as_read()
-        context["notifications"] = notifications
+        page = request.GET.get("page", 1)
+        paginator = Paginator(notifications, 50)
+        try:
+            notifications_list = paginator.page(page)
+        except PageNotAnInteger:
+            notifications_list = paginator.page(1)
+        except EmptyPage:
+            notifications_list = paginator.page(paginator.num_pages)
+        context["notifications"] = notifications_list
         return render(request, 'iconolab/user_notifications.html', context)
 
 
@@ -259,6 +266,20 @@
             comments_list = paginator.page(paginator.num_pages)
         context["comments"] = comments_list
         
+        if request.user.is_authenticated():
+            user_comment_notifications = Notification.objects.filter(
+                recipient=request.user, 
+                action_object_content_type__app_label="iconolab", 
+                action_object_content_type__model="iconolabcomment", 
+                target_content_type__app_label="iconolab",
+                target_content_type__model="annotation",
+                target_object_id=annotation.id
+            ).unread()
+            context["notifications_comments_ids"] = [int(val) for val in user_comment_notifications.values_list("action_object_object_id", flat=True)]
+            comment_list_ids = [comment.id for comment in context["comments"] ]
+            for notification in user_comment_notifications.all():
+                if int(notification.action_object_object_id) in comment_list_ids:
+                    notification.mark_as_read()
         
         image.stats.views_count += 1
         image.stats.save()
@@ -384,6 +405,19 @@
         context['revision'] = revision
         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(
+                    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 notified_revision:
+                notified_revision.first().mark_as_read()
+                context["notified_revision"] = True
         return render(request, 'iconolab/detail_revision.html', context)