merge
authorHarris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Thu, 18 Aug 2016 17:22:46 +0200
changeset 144 8a41415e1ab1
parent 143 c68983a2efac (current diff)
parent 142 93b706801905 (diff)
child 145 de5736883786
merge
--- a/README.md	Thu Aug 18 17:20:22 2016 +0200
+++ b/README.md	Thu Aug 18 17:22:46 2016 +0200
@@ -85,20 +85,22 @@
 	
 By default, the app is accessible through http://127.0.0.1:8000/home
 
+### 3. Importing data from CSV
 
-### 3. Fixture loaded data
+Make sure to have the following in the same folder:
 
-* User: contributeur1, password: firstuser
-* User: contributeur2, password: seconduser
+* All the images to import. The image names must match their respective item inventory number.
+* A csv file that contains the metadata for the items you will import
+* A json fixture file for initializing the collection in the database. (Optional if you want to import images in an existing collection)
+* A json fixture file for the metacategories that will be linked to the collection.
 
-* Collection (name): ingres
-* Image (ref): 1234567890 # You will need to move napoleon.jpg into web/media/uploads in order for the app to load the image properly
-* Annotation (guid): 34ae39ae-a9a2-4736-bc59-ba6f00e37f52
-
-To access the loaded annotation, follow:
+The following django manage.py command is used to import collection data and images:
 
-	/collections/ingres/images/26aec320-dcfe-4cbc-b912-6a6c13e8916e/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/detail
-	/collections/ingres/images/26aec320-dcfe-4cbc-b912-6a6c13e8916e/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/edit
+	python manage.py importimages <:export-csv-path> --encoding <:encoding> --collection-fixture <:collection_fixture_NAME> (OR --collection-id <:collection_id> --metacategories_fixture <:metacategories_fixture_NAME> 
 	
-The annotation owner is contributeur1, if you try to edit it as another user, it will create the revision but will not publish it in the current state of the project.
-	
+Notes: 
+* The export csv path will be used to find everything else (images and fixtures files). 
+* If the csv file is not encoded in utf-8, you MUST provide --encoding so the csv file can be read
+* You MUST provide either --collection-fixture or --collection-id, else the command doesn't know to which collection the objects will belong to.
+* The command will first parse the csv, then create the objects in the database (Item and ItemMetadata), then move the images to the settings.MEDIA_ROOT+/uploads/ folder after converting them to JPEG, then create the database objects for the images. The command will ignore any csv row that lacks an image or any csv row that already has a database entry for the collection (INV number is used to test if a database entry exists).
+
--- a/src/iconolab/forms/comments.py	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/forms/comments.py	Thu Aug 18 17:22:46 2016 +0200
@@ -8,13 +8,20 @@
 from django.utils.encoding import force_text
 from django.utils import timezone
 from iconolab.models import MetaCategory
+import logging
+
+logger = logging.getLogger(__name__)
 
 class IconolabCommentForm(XtdCommentForm):
-    metacategories = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=MetaCategory.objects.all(), required=False)
     email = forms.EmailField(required=False)
-    
+    metacategories = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=None, required=False)
+        
     def __init__(self, *args, **kwargs):
         super(IconolabCommentForm, self).__init__(*args, **kwargs)
+        self.collection = self.target_object.image.item.collection
+        logger.debug(self.fields)
+        self.fields["metacategories"].queryset = self.collection.metacategories.all()
+        logger.debug(self.fields["metacategories"].queryset)
         self.fields.pop('email')
     
     def get_comment_create_data(self):
--- a/src/iconolab/management/commands/importimages.py	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/management/commands/importimages.py	Thu Aug 18 17:22:46 2016 +0200
@@ -30,6 +30,12 @@
             default=False,
             help='insert extracted data into the specified collection instead of trying to load a collection fixture',
         )
+        parser.add_argument(
+            '--metacategories-fixture',
+            dest='metacategories_fixture',
+            default=False,
+            help='add metacategories to the created collection from a fixture file',
+        )
     
     def handle(self, *args, **options):
         pp = pprint.PrettyPrinter(indent=4)
@@ -60,6 +66,22 @@
                     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. !!!") 
+            
+            if options.get("metacategories_fixture"):
+                print("## Finding metacategories fixture json data in "+source_dir)
+                metacategories_fixture_path = os.path.join(source_dir, options.get("metacategories_fixture"))
+                if not os.path.isfile(metacategories_fixture_path):
+                    print("### No "+options.get("metacategories_fixture")+".json file was found in the source directory")
+                    raise ValueError("!!! Fixture file "+metacategories_fixture_path+" was not found !!!")
+                with open(metacategories_fixture_path) as metacategories_fixture_file:
+                    metacategories_data = json.loads(metacategories_fixture_file.read())
+                    for metacategory in metacategories_data:
+                        if options.get("collection_fixture") and metacategory["fields"].get("collection", False) != collection_data[0].get("pk"):
+                            print(metacategory["fields"].get("collection", False))
+                            raise ValueError("!!! The fixture should only contain metacategories for the imported collection !!!")
+                        elif options.get("collection_id") and metacategory["fields"].get("collection", False) != collection.id:
+                            raise ValueError("!!! The fixture should only contain metacategories for the imported collection !!!")
+            
             # 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")
@@ -97,6 +119,20 @@
                 collection = Collection.objects.get(
                     pk = collection_data[0]["pk"]
                 )
+                if collection.image:
+                    collection_image_path = os.path.join(settings.MEDIA_ROOT, str(collection.image))
+                    if not os.path.isfile(collection_image_path):
+                        print("### Moving collection image")
+                        _ , collection_image_name = os.path.split(collection_image_path)
+                        try:
+                            col_im = ImagePIL.open(os.path.join(source_dir, collection_image_name))
+                            print("##### Generating or copying jpeg for "+collection_image_name)
+                            col_im.thumbnail(col_im.size)
+                            col_im.save(collection_image_path, "JPEG", quality=100)
+                        except Exception as e:
+                            print(e)
+            if options.get("metacategories_fixture"):
+                call_command("loaddata", metacategories_fixture_path)
             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)
--- a/src/iconolab/models.py	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/models.py	Thu Aug 18 17:22:46 2016 +0200
@@ -266,6 +266,22 @@
         return self.revisions.filter(state=AnnotationRevision.AWAITING).distinct().count()
     
     @property
+    def accepted_revisions_count(self):
+        return self.revisions.filter(state=AnnotationRevision.ACCEPTED).distinct().count()
+    
+    @property
+    def rejected_revisions_count(self):
+        return self.revisions.filter(state=AnnotationRevision.REJECTED).distinct().count()
+    
+    @property
+    def studied_revisions_count(self):
+        return self.revisions.filter(state=AnnotationRevision.STUDIED).distinct().count()
+      
+    @property
+    def total_revisions_count(self):
+        return self.revisions.distinct().count()
+   
+    @property
     def collection(self):
         return self.image.collection
 
@@ -495,7 +511,7 @@
         (COLLECTION_ADMINS, 'collection admins'),
     )
     
-    collection = models.ForeignKey(Collection)
+    collection = models.ForeignKey(Collection, related_name="metacategories")
     label = models.CharField(max_length=255)
     triggers_notifications = models.IntegerField(choices=NOTIFIED_USERS, default=NONE)
     
--- a/src/iconolab/static/iconolab/css/iconolab.css	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/static/iconolab/css/iconolab.css	Thu Aug 18 17:22:46 2016 +0200
@@ -117,4 +117,29 @@
 	vertical-align: top; 
 	margin-top: 15px; 
 	margin-right: 15px;
+}
+
+/* COLLECTION HOME PAGE */
+.image-list-wrapper{
+	margin-top: 15px; 
+	margin-left:10px;
+}
+li.image-list-li{
+	margin-bottom: 5px; 
+	width: 370px; 
+	height: 350px; 
+	vertical-align:middle; 
+	padding:5px;
+}
+.image-list-image-container{
+	 position: relative;
+}
+.object-info{
+	margin-bottom:10px;
+}
+.collection-home-item-btn{
+	margin-bottom:5px;
+}
+.collection-home-tab{
+	cursor: pointer;
 }
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/collection_home.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/iconolab/collection_home.html	Thu Aug 18 17:22:46 2016 +0200
@@ -7,27 +7,60 @@
 {% load iconolab_tags %}
 
 {% block content %}
-  <h2>Fonds {{collection.verbose_name}}</h2>
-  
- 
-  <p><strong>Images du fonds</strong></p>
-<ul class="image-list-wrapper list-inline">
+<h2>{{collection.verbose_name}}</h2><br>
+
+<ul class="nav nav-tabs">
+  <li id="tab-recent" role="presentation" {% if recent_annotations %} class="active" {% endif %}><a class="collection-home-tab" id="show-recent">Annotations récentes</a></li>
+  <li id="tab-revised" role="presentation"><a class="collection-home-tab" id="show-revised">Annotations les plus révisées</a></li>
+  <li id="tab-contribution" role="presentation"><a class="collection-home-tab" id="show-contribution">Appels à contribution</a></li>
+  <li id="tab-items" role="presentation" class="{% if not recent_annotations %}active{% endif %} pull-right"><a class="collection-home-tab" id="show-items">Objets du fond {{collection.verbose_name}}</a></li>
+</ul>
+
+<div id="list-recent" class="recent-ann-wrapper collection-home-block {% if recent_annotations %}selected{% endif %}">
+  {% include "partials/image_annotations_list.html" with annotation_list=recent_annotations.all %}
+</div>
 
+<div id="list-revised" class="revised-ann-wrapper collection-home-block">
+  {% include "partials/image_annotations_list.html" with annotation_list=revised_annotations.all %}
+</div>
+
+<div id="list-contribution" class="contribution-ann-wrapper collection-home-block">
+  {% include "partials/image_annotations_list.html" with annotation_list=contribution_calls_annotations_list %}
+</div>
+
+<div id="list-items" class="collection-home-block {% if not recent_annotations %}selected{% endif %}">
+<ul class="image-list-wrapper list-inline">
   {% for item in collection.items.all %}
-  <li class="small-image-wrapper" style="margin-bottom: 5px;">
-    <div class="image-container text-center" style="position: relative">
+  <li class="image-list-li small-image-wrapper panel panel-default">
+    <div class="image-container text-center image-list-image-container">
     {% with item.images.first as image %}
-      {% thumbnail image.media "300x300" crop=False as im %}
+      {% thumbnail image.media "350x300" crop=False as im %}
+        <div class="object-infos">
+          <a class="btn btn-default btn-xs collection-home-item-btn" href="{% url 'item_detail' collection_name item.item_guid %}"><i class="fa fa-eye"></i> Détail de l'objet</a>
+        </div>
+        <div>
         <a href="{% url 'item_detail' collection_name item.item_guid %}">
           <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
         </a>
+        </div>
       {% endthumbnail %}
-        <div class="object-infos">
-          <a class="btn btn-default btn-xs collection-home-btn" href="{% url 'item_detail' collection_name item.item_guid %}"><i class="fa fa-eye"></i> Détail de l'objet</a>
-        </div>
     {% endwith %}
    </li>
 {% endfor %}
 </ul>
+</div>
 
+{% endblock %}
+
+{% block footer_js %}
+<script>
+  $(".collection-home-block:not(.selected)").hide();
+  $(".collection-home-tab").click(function(){
+      $(".collection-home-tab").parent().removeClass("active");
+      $(this).parent().addClass("active");
+      clickedTab = /show\-([0-9a-z\-]+)/.exec($(this).attr("id"))[1];
+      $(".collection-home-block").hide();
+      $("#list-"+clickedTab).show();
+  })
+</script>
 {% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/detail_annotation.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_annotation.html	Thu Aug 18 17:22:46 2016 +0200
@@ -32,7 +32,7 @@
       
             </div>
         <div v-show="!showZoom" id="detail-annotation" class='col-md-6' style="">
-            <h4>Annotation créée par {{ annotation.author.username }}</h4>    
+            <h4>Annotation créée par <a href="{% url 'user_home' annotation.author.id %}">{{ annotation.author.username }}</a></h4>    
   		    <p><strong>Titre:</strong> {{ annotation.current_revision.title }}</p>
   		    <p><strong>Description:</strong> {{ annotation.current_revision.description }}</p>
             {% if tags_data != "[]" %}
@@ -82,7 +82,7 @@
                 <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 %}
-                <div id="c{{comment.id}}-subtext" class="comment-subtext">{{ comment.submit_date|date:"d/m/Y" }} à {{ comment.submit_date|time:"H:i" }}&nbsp;-&nbsp; <b>{{ comment.name }}</b></div>
+                <div id="c{{comment.id}}-subtext" class="comment-subtext">{{ comment.submit_date|date:"d/m/Y" }} à {{ comment.submit_date|time:"H:i" }}&nbsp;-&nbsp; <b><a href="{% url 'user_home' comment.user.id %}">{{comment.name}}</a></b></div>
                 <div id="c{{comment.id}}-label-list" class="comment-metacategories">
                 {% if comment.revision or comment.metacategories.count > 0 %}&nbsp;-&nbsp;{% endif %}
                 {% if comment.revision %}
--- a/src/iconolab/templates/iconolab/detail_image.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_image.html	Thu Aug 18 17:22:46 2016 +0200
@@ -21,6 +21,6 @@
 	</div>
 </div>
 
-{% include "partials/image_annotations_list.html" with annotation_list=image.annotations.all %}
+{% include "partials/image_annotations_list.html" with annotation_list=image.annotations.all header="Annotations de l'image" %}
 
 {% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/detail_item.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_item.html	Thu Aug 18 17:22:46 2016 +0200
@@ -53,7 +53,7 @@
     </div>
       {% for image in item.images.all %}  
       <div id="annotations-{{image.image_guid}}" class="col-md-12 image-annotations-list">
-        {% include "partials/image_annotations_list.html" with annotation_list=image.annotations.all image_guid=image.image_guid %}  
+        {% include "partials/image_annotations_list.html" with annotation_list=image.annotations.all header="Annotation de l'image" %}  
       </div>
       {% endfor %}  
 </div>
--- a/src/iconolab/templates/partials/header_search_form.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/partials/header_search_form.html	Thu Aug 18 17:22:46 2016 +0200
@@ -16,5 +16,5 @@
 		</select>
 	</div>
 
-	<button type="submit" class="btn btn-default">Rechercher</button>
+	<button type="submit" class="btn btn-default btn-sm"><i class="fa fa-search" aria-hidden="true"></i></button>
 </form>
--- a/src/iconolab/templates/partials/image_annotations_list.html	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/templates/partials/image_annotations_list.html	Thu Aug 18 17:22:46 2016 +0200
@@ -2,10 +2,13 @@
 {% load iconolab_tags %}
 
 <ul class="annotation-list-wrapper list-inline">
-
-	<h4><strong>Annotations de l'image</strong></h4>  
+    {% if header %}
+	<h4><strong>Annotations de l'image</strong></h4>
+    {% endif %}
+    
+    
     {% if not annotation_list %}
-        <p> Aucune annotation pour cette image </p>
+        <h3 class="text-center"><small>Aucune annotation à afficher</small></p>
     {% else %}
         <table class="table table-condensed">
           <thead>
@@ -22,7 +25,7 @@
             <td>
               <div class="fragment-container" style="position: relative">
                 {% thumbnail annotation.image.media "150x150" crop=False as im %}
-                  <a href="{% url 'annotation_detail' collection_name image_guid annotation.annotation_guid %}">
+                  <a href="{% url 'annotation_detail' 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 %})">
@@ -34,12 +37,12 @@
               </div>
             </td>
             <td>{{ annotation.current_revision.title }}</td>
-            <td>{{ annotation.author }}</td>
+            <td><a href="{% url 'user_home' annotation.author.id %}" style="color:#000000">{{ annotation.author }}</a></td>
             <td>{{ annotation.created|date:'d-m-Y' }}</td>
             <td>{{ annotation.current_revision.created|date:'d-m-Y' }}</td>
             <td>
               {% for contributor in annotation.stats.contributors.all %}
-                  {{ contributor.username }}{% if not forloop.last %}, {% endif %}
+                  <a href="{% url 'user_home' contributor.id %}" style="color:#000000">{{ contributor.username }}</a>{% if not forloop.last %}, {% endif %}
               {% endfor %}
             </td>
             <td>
--- a/src/iconolab/views/iconolab.py	Thu Aug 18 17:20:22 2016 +0200
+++ b/src/iconolab/views/iconolab.py	Thu Aug 18 17:22:46 2016 +0200
@@ -1,5 +1,6 @@
 from django.shortcuts import HttpResponse, get_object_or_404, render
 from django.http import Http404
+from django.db.models import Count
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.models import User
 from django.views.generic import View, DetailView, RedirectView, TemplateView
@@ -20,13 +21,13 @@
 class GlobalHomepageView(View):
     def get(self, request, *args, **kwargs):
         context = {}
-        context["collections"] = Collection.objects
+        context['collections'] = Collection.objects
         return render(request, 'iconolab/home.html', context)
 
 
 class UserHomeView(DetailView):
     model = User
-    slug_field = "id"
+    slug_field = 'id'
     
     def get_context_data(self, **kwargs):
         context = super(UserHomeView, self).get_context_data(**kwargs)
@@ -36,34 +37,34 @@
         self.object = self.get_object()
         context = self.get_context_data()
         profile_user = self.object
-        context["profile_user"] = profile_user
-        context["user_annotations"] = Annotation.objects.filter(author=profile_user).prefetch_related(
-            "current_revision", 
-            "revisions", 
-            "image", 
-            "image__item", 
-            "image__item__collection"
+        context['profile_user'] = profile_user
+        context['user_annotations'] = Annotation.objects.filter(author=profile_user).prefetch_related(
+            'current_revision', 
+            'revisions', 
+            'image', 
+            'image__item', 
+            'image__item__collection'
         )
-        context["user_revisions_annotations"] = Annotation.objects.filter(revisions__author=profile_user).exclude(author=profile_user).prefetch_related(
-            "current_revision", 
-            "revisions", 
-            "image", 
-            "image__item", 
-            "image__item__collection"
+        context['user_revisions_annotations'] = Annotation.objects.filter(revisions__author=profile_user).exclude(author=profile_user).prefetch_related(
+            'current_revision', 
+            'revisions', 
+            'image', 
+            'image__item', 
+            'image__item__collection'
         ).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_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).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"
+        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):
+            if request.GET.get('clear_notifications', False):
                 Notification.objects.filter(recipient=request.user).mark_all_as_read()
-            context["notifications"] = Notification.objects.filter(recipient=request.user)
+            context['notifications'] = Notification.objects.filter(recipient=request.user)
         return render(request, 'iconolab/user_home.html', context)
 
 class UserNotificationsView(View):
@@ -71,8 +72,8 @@
     def get(self, request, *args, **kwargs):
         context = {}
         notifications = Notification.objects.filter(recipient=request.user)
-        context["notifications_unread_ids"] = notifications.unread().values_list("id", flat=True)
-        page = request.GET.get("page", 1)
+        context['notifications_unread_ids'] = notifications.unread().values_list('id', flat=True)
+        page = request.GET.get('page', 1)
         paginator = Paginator(notifications, 50)
         try:
             notifications_list = paginator.page(page)
@@ -80,38 +81,38 @@
             notifications_list = paginator.page(1)
         except EmptyPage:
             notifications_list = paginator.page(paginator.num_pages)
-        context["notifications"] = notifications_list
+        context['notifications'] = notifications_list
         return render(request, 'iconolab/user_notifications.html', context)
 
 # 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):
-        """
+        '''
             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():
+        if 'collection_name' in kwargs.keys():
             try:
-                objects_tuple += (Collection.objects.prefetch_related("items", "items__images").get(name=kwargs.get('collection_name')),)
+                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():
+        if 'item_guid' in kwargs.keys():
             try:
-                objects_tuple += (Item.objects.prefetch_related("images").get(item_guid=kwargs.get('item_guid')),)
+                objects_tuple += (Item.objects.prefetch_related('images', 'metadatas', 'images__stats').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():
+        if 'image_guid' in kwargs.keys():
             try:
-                objects_tuple += (Image.objects.prefetch_related("annotations", "item").get(image_guid=kwargs.get('image_guid')),)
+                objects_tuple += (Image.objects.prefetch_related('annotations', 'item', 'stats').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():
+        if 'annotation_guid' in kwargs.keys():
             try:
-                objects_tuple += (Annotation.objects.select_related('current_revision').get(annotation_guid=kwargs.get('annotation_guid')),)
+                objects_tuple += (Annotation.objects.select_related('current_revision', 'stats', 'image').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():
+        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):
@@ -128,6 +129,29 @@
         context = super(CollectionHomepageView, self).get_context_data(**kwargs)
         context['collection_name'] = self.kwargs.get('collection_name', '')
         context['collection'] = collection
+        
+        # Recent annotations
+        context['recent_annotations'] = Annotation.objects.filter(image__item__collection__name=collection.name).prefetch_related(
+            'current_revision', 
+            'stats'
+        ).order_by('-current_revision__created')
+        
+        # Recent annotations
+        context['revised_annotations'] = Annotation.objects.filter(image__item__collection__name=collection.name).prefetch_related(
+            'current_revision', 
+            'stats'
+        ).annotate(revision_count=Count("revisions")).order_by('-revision_count')
+        
+        contrib_calls_annotations_ids = list(set(MetaCategoryInfo.objects.filter(
+            metacategory__collection__name=collection.name, 
+            metacategory__triggers_notifications=MetaCategory.CONTRIBUTORS
+        ).order_by("comment__submit_date").values_list("comment__object_pk", flat=True)))
+        
+        collection_annotations = Annotation.objects.filter(id__in=contrib_calls_annotations_ids).all()
+        logger.debug(collection_annotations)
+        collection_ann_dict = dict([(str(annotation.id), annotation) for annotation in collection_annotations])
+        context["contribution_calls_annotations_list"] = [collection_ann_dict[id] for id in contrib_calls_annotations_ids]
+        
         return render(request, 'iconolab/collection_home.html', context)
     
 
@@ -239,9 +263,9 @@
         context['annotation'] = annotation
         context['tags_data'] = annotation.current_revision.get_tags_json()
         
-        page = request.GET.get("page", 1)
-        per_page = request.GET.get("perpage", 10)
-        full_comments_list = IconolabComment.objects.for_app_models("iconolab.annotation").filter(object_pk = annotation.pk).order_by("thread_id", "-order")
+        page = request.GET.get('page', 1)
+        per_page = request.GET.get('perpage', 10)
+        full_comments_list = IconolabComment.objects.for_app_models('iconolab.annotation').filter(object_pk = annotation.pk).order_by('thread_id', '-order')
         paginator = Paginator(full_comments_list, per_page)
         try:
             comments_list = paginator.page(page)
@@ -249,19 +273,19 @@
             comments_list = paginator.page(1)
         except EmptyPage:
             comments_list = paginator.page(paginator.num_pages)
-        context["comments"] = comments_list
+        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",
+                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"] ]
+            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()
@@ -359,29 +383,29 @@
         if request.user.is_authenticated() and annotation.author == request.user:
             ann_author_notified = Notification.objects.filter(
                     recipient=request.user, 
-                    action_object_content_type__app_label="iconolab", 
-                    action_object_content_type__model="annotationrevision",
+                    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_content_type__app_label='iconolab',
+                    target_content_type__model='annotation',
                     target_object_id=annotation.id
                 ).unread()
             if ann_author_notified:
                 ann_author_notified.first().mark_as_read()
-                context["notified_revision"] = True
+                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_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_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
+                context['notified_revision'] = True
         return render(request, 'iconolab/detail_revision.html', context)
 
         
@@ -414,7 +438,7 @@
                 )
             )(request)
         # Auto-accepts the revision only if the proper query arg is set and only if the revision parent is the current revision
-        if "auto_accept" in request.GET and request.GET["auto_accept"] in ["True", "true", "1", "yes"] and revision.parent_revision == annotation.current_revision:
+        if 'auto_accept' in request.GET and request.GET['auto_accept'] in ['True', 'true', '1', 'yes'] and revision.parent_revision == annotation.current_revision:
             annotation.validate_existing_revision(revision)
             return RedirectView.as_view(
                 url=reverse('annotation_detail', 
@@ -426,7 +450,7 @@
                 )
             )(request)
         # Auto-reject the revision only if the proper query arg is set
-        if "auto_reject" in request.GET and request.GET["auto_reject"] in ["True", "true", "1", "yes"]:
+        if 'auto_reject' in request.GET and request.GET['auto_reject'] in ['True', 'true', '1', 'yes']:
             annotation.reject_existing_revision(revision) 
             return RedirectView.as_view(
                 url=reverse('annotation_detail', 
@@ -510,7 +534,7 @@
     
     
 class NotFoundErrorView(TemplateView):
-    template_name="errors/404error.html"
+    template_name='errors/404error.html'
     
 class HelpView(TemplateView):
-    template_name="iconolab/glossary.html"
\ No newline at end of file
+    template_name='iconolab/glossary.html'
\ No newline at end of file