Simplify collection admin view, add buttons to accept/reject revisions.
--- 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