Simplify collection admin view, add buttons to accept/reject revisions.
authorAlexandre Segura <mex.zktk@gmail.com>
Thu, 27 Apr 2017 13:10:38 +0200
changeset 477 7b93c25f2b74
parent 476 8e706eb56e6e
child 478 5637f918384a
Simplify collection admin view, add buttons to accept/reject revisions.
src/iconolab/templates/iconolab/detail_revision.html
src/iconolab/templates/iconolab/user_collection_admin.html
src/iconolab/templates/iconolab/user_collections.html
src/iconolab/templates/registration/created.html
src/iconolab/views/userpages.py
--- a/src/iconolab/templates/iconolab/detail_revision.html	Tue Apr 25 17:49:56 2017 +0200
+++ b/src/iconolab/templates/iconolab/detail_revision.html	Thu Apr 27 13:10:38 2017 +0200
@@ -24,15 +24,19 @@
 						</svg>
 					{% endthumbnail %}
 				</div>
-                <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="">
+        <br>
+        <ul class="list-unstyled">
+          <li><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></li>
+          <li><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></li>
+          <li></li>
+        </ul>
+
+      </div>
+			<div id="revision-detail" class="col-xs-6">
                 <h4>Annotation révisée par {{ revision.author.username}} </h4>
-				<p> <strong>Titre :</strong> {{ revision.title }}</p>
-				<p> <strong>Description:</strong> {{ revision.description }}</p>
-				<p><strong>Mots-clés :</strong></p>
+        				<p> <strong>Titre :</strong> {{ revision.title }}</p>
+        				<p> <strong>Description:</strong> {{ revision.description }}</p>
+        				<p><strong>Mots-clés :</strong></p>
                 <typeahead :read-only="1" :tags="{{ tags_data }}"></typeahead>
                 <br>
                 <div class="alert alert-info" role="alert">
@@ -58,28 +62,29 @@
                     {% endif %}
                   </ul>
                 {% endif %}
-                  {% if revision.state == 0 and user == annotation.author %}
-                  <br>
-                  <div class="btn-group" role="group">
+                  {% if revision.state == 0 %}
+                    {% if user == annotation.author or user.is_staff %}
+                    <p>
+                      {% if revision.parent_revision == annotation.current_revision %}
+                      <button id="accept-revision" type="button" class="btn btn-success"><span class="glyphicon glyphicon-ok"></span> Accepter</button>
+                      {% endif %}
+                      <button id="reject-revision" type="button" class="btn btn-danger"><span class="glyphicon glyphicon-remove"></span> Rejeter</button>
+                      <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}"><span class="glyphicon glyphicon-eye-open"></span> Voir les différences</a>
+                    </p>
                     {% if revision.parent_revision == annotation.current_revision %}
-                      <button id="accept-revision" type="button" class="btn btn-default"><span class="glyphicon glyphicon-ok"></span> Accepter cette révision</button>
+                      <div id="accept-info" class="alert alert-success alert-merge">
+                        <p>Si vous acceptez cette proposition, elle deviendra automatiquement la version courante de l'annotation.</p>
+                        <br>
+                        <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}?auto_accept=True">Confirmer</a>
+                      </div>
                     {% endif %}
-                    <button id="merge-revision" type="button" class="btn btn-default"><span class="glyphicon glyphicon-eye-open"></span> Etudier cette révision</button>
-                    <button id="reject-revision" type="button" class="btn btn-default"><span class="glyphicon glyphicon-remove"></span> Rejeter cette révision</button>
-                  </div><br><br>
-
-                  {% if revision.parent_revision == annotation.current_revision %}
-                    <div id="accept-info" class="alert alert-warning alert-merge"> Si vous acceptez cette proposition, elle deviendra automatiquement la version courante de l'annotation.<br><br>
-                       <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}?auto_accept=True"> Valider </a>
+                    <div id="reject-info" class="alert alert-danger alert-merge">
+                      <p>Attention: l'action de rejeter une proposition est irréversible.</p>
+                      <br>
+                      <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}?auto_reject=True">Confirmer</a>
                     </div>
+                    {% endif %}
                   {% endif %}
-                  <div id="reject-info" class="alert alert-warning alert-merge"> Attention: l'action de rejeter une proposition est irréversible.<br><br>
-                     <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}?auto_reject=True"> Valider </a>
-                  </div>
-                  <div id="merge-info" class="alert alert-warning alert-merge"> En étudiant la révision, vous accédez à une interface qui vous permet notamment de comparer la proposition avec la version courante de l'annotation avant d'en accepter tout ou partie. <br><br>
-                     <a class="btn btn-default" href="{% url 'annotation_merge' collection_name image_guid annotation_guid revision_guid %}"> Valider </a>
-                  </div>
-                {% endif %}
 
           </div>
 		</div>
@@ -90,8 +95,7 @@
   <script>
     new Vue({
       el: "#revision-detail",
-      components: {'Typeahead': iconolab.VueComponents.Typeahead },
-      methods: { yo: function(){alert(1);} }
+      components: {'Typeahead': iconolab.VueComponents.Typeahead }
     });
     $(".alert-merge").hide();
     $("#accept-revision").click(function(){
@@ -100,12 +104,6 @@
             $("#accept-info").slideDown();
         }
     });
-    $("#merge-revision").click(function(){
-        if($("#merge-info").is(":hidden")){
-            $(".alert-merge").hide();
-            $("#merge-info").slideDown();
-        }
-    });
     $("#reject-revision").click(function(){
         if($("#reject-info").is(":hidden")){
             $(".alert-merge").hide();
--- a/src/iconolab/templates/iconolab/user_collection_admin.html	Tue Apr 25 17:49:56 2017 +0200
+++ b/src/iconolab/templates/iconolab/user_collection_admin.html	Thu Apr 27 13:10:38 2017 +0200
@@ -1,4 +1,4 @@
-{% extends 'iconolab_base.html' %}
+{% extends 'iconolab/user_base.html' %}
 
 {% load staticfiles %}
 
@@ -8,41 +8,49 @@
 
 {% load notifications_tags %}
 
-{% block content %}
+{% block user_content %}
   <div id="user-profile-block">
-    <h3>Tableau de bord - Fonds {{collection.verbose_name}}</h3>
-    <h2><small>Filtres et tri</small></h2>
-    <form class="form" method="GET" target="">
-      <div class="row">
-        <div class="form-group col-md-4">
-          <label>Trier par
-          </label><br>
-          <select id="first_ordering_select" name="first" class="form-control">
-            <option id="first_oldest" value="oldest" {% if qarg_first == 'oldest'%}selected{% endif %}>Date (plus vieux d'abord)</option>
-            <option id="first_recent" value="recent" {% if qarg_first == 'recent'%}selected{% endif %}>Date (plus récents d'abord)</option>
-            <option id="first_most_viewed" value="most_viewed" {% if qarg_first == 'most_viewed'%}selected{% endif %}>Nombre de vues</option>
-            <option id="first_most_commented" value="most_commented" {% if qarg_first == 'most_commented'%}selected{% endif %}>Nombre de commentaires</option>
-            <option id="first_most_revised" value="most_revised" {% if qarg_first == 'most_revised'%}selected{% endif %}>Nombre de révisions</option>
-            <option id="first_most_tagged" value="most_tagged" {% if qarg_first == 'most_tagged'%}selected{% endif %}>Nombre de mots-clés</option>
-          </select>
-        </div>
-        <div class="form-group col-md-6">
-          <label>Filtrer par</label><br>
-          <b>Mots-clés pertinents:</b> qualification minimale <input type="number" name="min_relevancy" min="0" max="5" {% if qarg_min_relevancy %}value='{{qarg_min_relevancy}}'{% endif %}></input><br>
-          <b>Mots-clés fiables:</b> qualification minimale <input type="number" name="min_accuracy" min="0" max="5" {% if qarg_min_accuracy %}value='{{qarg_min_accuracy}}'{% endif %}></input><br>
-          <b>Nombre de commentaires:</b> au moins <input type="number" name="min_comments" min="0" {% if qarg_min_comments %}value='{{qarg_min_comments}}'{% endif %}></input><br>
-          <b>Nombre de révisions:</b> au moins <input type="number" name="min_revisions" min="0" {% if qarg_min_revisions %}value='{{qarg_min_revisions}}'{% endif %}></input><br>
-          <b>Métacatégories:</b><br>
-          {% for metacategory in collection.metacategories.all %}
-            {% with 'min_metacategory_'|addstr:metacategory.id as mtcg_input_name %}
-              au moins <input type="number" name="{{mtcg_input_name}}" min="0" {% if qarg_metacategories_filters %}value='{{qarg_metacategories_filters|get_item:mtcg_input_name}}'{% endif %}></input> {{metacategory.label}}<br>
-            {% endwith %}
-          {% endfor %}
-        </div>
-      </div>
-      <input class="btn btn-default" type="submit" value="Filtrer"></input>
-    </form>
-    <h2><small>Listes des annotations</small></h2>
-    {% include "partials/image_annotations_list.html" with annotation_list=collection_filtered_annotations collection_name=collection.name show_links=True %}
+    <h4>Révisions en attente de validation</h4>
+    <table class="table">
+      <thead>
+        <th></th>
+        <th>Titre</th>
+        <th>Auteur</th>
+        <th>Date</th>
+        <th></th>
+      </thead>
+      <tbody>
+      {% for revision in awaiting_revisions %}
+      <tr>
+        <td>
+          <div class="fragment-container" style="position: relative">
+            {% thumbnail revision.annotation.image.media "100x100" crop=False as im %}
+              <a href="{% url 'revision_detail' collection.name revision.annotation.image.image_guid revision.annotation.annotation_guid revision.revision_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="{{ revision.fragment|clean_path }}" opacity="0.7" fill="orange"></path>
+                  </g>
+                </svg>
+              </a>
+            {% endthumbnail %}
+          </div>
+        </td>
+        <td>{{ revision.title }}</td>
+        <td>{{ revision.author }}</td>
+        <td>{{ revision.created|date:'d-m-Y H:i:s' }}</td>
+        <td>
+          <span class="pull-right">
+            {% if revision.parent_revision == revision.annotation.current_revision %}
+            <a class="btn btn-sm btn-success" href="{% url 'annotation_merge' collection.name revision.annotation.image.image_guid
+            revision.annotation.annotation_guid revision.revision_guid %}?auto_accept=True"><span class="glyphicon glyphicon-ok"></span> Accepter</a>
+            {% endif %}
+            <a class="btn btn-sm btn-danger" href="{% url 'annotation_merge' collection.name revision.annotation.image.image_guid revision.annotation.annotation_guid revision.revision_guid %}?auto_reject=True"><span class="glyphicon glyphicon-remove"></span> Refuser</a>
+          </span>
+        </td>
+      </tr>
+      {% endfor %}
+      </tbody>
+    </table>
   </div>
 {% endblock %}
--- a/src/iconolab/templates/iconolab/user_collections.html	Tue Apr 25 17:49:56 2017 +0200
+++ b/src/iconolab/templates/iconolab/user_collections.html	Thu Apr 27 13:10:38 2017 +0200
@@ -1,11 +1,26 @@
 {% extends 'iconolab/user_base.html' %}
 
+{% load thumbnail %}
+
 {% block user_content %}
   <h2>Administration de Fonds</h2>
   {% for collection in profile_user.profile.managed_collections.all %}
-  <h3>{{collection.verbose_name}}</h3>
-  <div>
-    <a href="{% url 'user_admin_panel' collection.name %}" class="btn btn-default text-center">Tableau de bord</a>
+  <div class="panel panel-default">
+    <div class="panel-body">
+      {% thumbnail collection.image "100x100" crop="center" as img %}
+        <a class="pull-left" href="{% url 'user_admin_panel' collection.name %}" style="margin-right: 15px;">
+          <img src="{{ img.url }}" class="img-responsive">
+        </a>
+      {% endthumbnail %}
+      <h4 class="pull-left">{{collection.verbose_name}}</h4>
+      <a href="{% url 'user_admin_panel' collection.name %}" class="btn btn-default pull-right">Tableau de bord</a>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-md-3">
+    </div>
+    <div class="col-md-9">
+    </div>
   </div>
   {% endfor %}
 {% endblock %}
--- a/src/iconolab/templates/registration/created.html	Tue Apr 25 17:49:56 2017 +0200
+++ b/src/iconolab/templates/registration/created.html	Thu Apr 27 13:10:38 2017 +0200
@@ -10,16 +10,16 @@
     <h2>Votre compte a été créé! <small>Bienvenue sur Iconolab</small></h2><br>
     <ul class="list-inline">
       <li>
-        <a class="btn btn-default" href="{% url 'user_home' user.id %}">
-          <span class="glyphicon glyphicon-user" aria-hidden="true"></span>  
+        <a class="btn btn-default" href="{% url 'user_home' user.username %}">
+          <span class="glyphicon glyphicon-user" aria-hidden="true"></span>
           Ma page de profil
         </a>
       </li>
       <li><a class="btn btn-default" href="{% url 'home' %}">
-          <span class="glyphicon glyphicon-home" aria-hidden="true"></span>  
+          <span class="glyphicon glyphicon-home" aria-hidden="true"></span>
           Page d'accueil d'Iconolab
         </a></li>
     </ul>
 </div>
 
-{% endblock %}
\ No newline at end of file
+{% endblock %}
--- a/src/iconolab/views/userpages.py	Tue Apr 25 17:49:56 2017 +0200
+++ b/src/iconolab/views/userpages.py	Thu Apr 27 13:10:38 2017 +0200
@@ -9,7 +9,7 @@
 from django.conf import settings
 from django.urls import reverse
 from notifications.models import Notification
-from iconolab.models import Collection, Annotation, IconolabComment, Image, MetaCategoriesCountInfo
+from iconolab.models import Collection, Annotation, AnnotationRevision, IconolabComment, Image, MetaCategoriesCountInfo
 from iconolab.auth.forms import UserForm
 from itertools import chain
 from uuid import UUID
@@ -173,97 +173,26 @@
     """
         View that displays the admin panel, allowing collection admin to filter and order annotations on several criterias
     """
-    # model = User
-    # slug_field = 'id'
+    def get(self, request, *args, **kwargs):
 
-    # def get_context_data(self, **kwargs):
-    #     context = super(UserCollectionAdminView, self).get_context_data(**kwargs)
-    #     return context
-
-    def get(self, request, *args, **kwargs):
-        # self.object = self.get_object()
         profile_user = request.user
-        # context = self.get_context_data()
         context = {}
         collection_name = kwargs.get("collection_name")
         collection_qs = Collection.objects.filter(name=collection_name)
         if not request.user.is_staff and not request.user.is_authenticated or profile_user != request.user or not collection_qs.exists():
-            return redirect(reverse_lazy('user_home', kwargs={'slug': profile_user.id}))
+            return redirect(reverse_lazy('user_home', kwargs={'slug': profile_user.username}))
         collection = collection_qs.first()
         if collection not in profile_user.profile.managed_collections.all():
-            return redirect(reverse_lazy('user_home', kwargs={'slug': profile_user.id}))
-
-        annotation_queryset = Annotation.objects.distinct().filter(image__item__collection=collection).prefetch_related('current_revision', 'stats', 'image', 'image__item')
+            return redirect(reverse_lazy('user_home', kwargs={'slug': profile_user.username}))
 
-        # filtering
-        comments_count_filter = request.GET.get("min_comments", "")
-        if comments_count_filter and comments_count_filter.isdigit():
-            comments_count_filter = int(comments_count_filter)
-            context["qarg_min_comments"] = comments_count_filter
-            annotation_queryset = annotation_queryset.filter(stats__comments_count__gte=comments_count_filter)
-        revisions_count_filter = request.GET.get("min_revisions", "")
-        if revisions_count_filter and revisions_count_filter.isdigit():
-            revisions_count_filter = int(revisions_count_filter)
-            context["qarg_min_revisions"] = revisions_count_filter
-            annotation_queryset = annotation_queryset.filter(stats__submitted_revisions_count__gte=revisions_count_filter)
-        relevancy_filter = request.GET.get("min_relevancy", "")
-        if relevancy_filter and relevancy_filter.isdigit():
-            min_relevancy = min(int(relevancy_filter), 5)
-            context["qarg_min_relevancy"] = relevancy_filter
-            annotation_queryset = annotation_queryset.filter(current_revision__tagginginfo__relevancy__gte=min_relevancy)
-        accuracy_filter = request.GET.get("min_accuracy", "")
-        if accuracy_filter and accuracy_filter.isdigit():
-            min_accuracy = min(int(accuracy_filter), 5)
-            context["qarg_min_accuracy"] = min_accuracy
-            annotation_queryset = annotation_queryset.filter(current_revision__tagginginfo__accuracy__gte=min_accuracy)
+        context['profile_user'] = profile_user
+        context['collection'] = collection
 
-        metacategories_filter = []
-        mtcg_annotations_ids = {}
-        filtering_on_metacategories = False
-        context["qarg_metacategories_filters"] = {}
-        for metacategory in collection.metacategories.all():
-            # Default filter is 0
-            mtcg_filter = request.GET.get("min_metacategory_"+str(metacategory.id), "0")
-            # We ignore filters that aren't integers
-            if mtcg_filter and mtcg_filter.isdigit():
-                # For each metacategory we have a dict entry with key=id that will be a list of the annotation matching the filter
-                mtcg_annotations_ids[str(metacategory.id)] = []
-                # Queryarg for autocompleting the form on page load
-                context["qarg_metacategories_filters"]["min_metacategory_"+str(metacategory.id)] = mtcg_filter
-                # If we got this far we did a filter on metacategories
-                filtering_on_metacategories = True
-                for annotation in annotation_queryset.all():
-                    if int(mtcg_filter) == 0 or MetaCategoriesCountInfo.objects.filter(metacategory=metacategory, annotation_stats_obj=annotation.stats, count__gte=int(mtcg_filter)).exists():
-                         mtcg_annotations_ids[str(metacategory.id)].append(annotation.annotation_guid)
-        if filtering_on_metacategories:
-            # If we did a filter on metacategories we have several list of matching annotations. We only display the intersection of all these lists.
-            annotation_queryset = annotation_queryset.filter(annotation_guid__in=
-                list(
-                     set.intersection(*[
-                        set(value) for value in list(mtcg_annotations_ids.values())
-                    ])
-                )
-            )
+        context['awaiting_revisions'] = AnnotationRevision.objects.filter(
+            state=AnnotationRevision.AWAITING,
+            annotation__image__item__collection=collection
+        ).order_by('-created')
 
-        # ordering
-        ordering = []
-        orderby_map = {
-            "oldest": "created",
-            "recent": "-created",
-            "most_commented": "-stats__comments_count",
-            "most_tagged": "-stats__tag_count",
-            "most_revised": "-stats__submitted_revisions_count",
-            "most_viewed": "-stats__views_count"
-        }
-        for ordering_qarg in ["first", "second", "third", "fourth"]:
-            if request.GET.get(ordering_qarg, "") in ["oldest", "recent", "most_commented", "most_tagged", "most_revised", "most_viewed"] and orderby_map.get(request.GET.get(ordering_qarg)) not in ordering:
-                context["qarg_"+ordering_qarg] = request.GET.get(ordering_qarg)
-                ordering.append(orderby_map.get(request.GET.get(ordering_qarg)))
-        annotation_queryset = annotation_queryset.order_by(*ordering)
-        context["collection_filtered_annotations"] = annotation_queryset
-        context["collection"] = collection
-        logger.debug(ordering)
-        logger.debug(annotation_queryset)
         return render(request, 'iconolab/user_collection_admin.html', context)
 
 class UserSettingsView(View):
@@ -283,7 +212,7 @@
         if user_form.is_valid():
             user = user_form.save(commit=False)
             user.save()
-            return redirect(reverse('user_settings', kwargs={'slug': user.id}))
+            return redirect(reverse('user_settings', kwargs={'slug': user.username}))
 
         context['user_form'] = user_form