src/iconolab/views/objects.py
author ymh <ymh.work@gmail.com>
Thu, 02 Aug 2018 16:15:39 +0200
changeset 593 f8310b7ddef0
parent 564 8f77d410f56e
permissions -rw-r--r--
Added tag 0.1.10 for changeset a87ffe8e08e5

import logging

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.urls import reverse
from django.db.models import Count, Prefetch
from django.http import Http404
from django.shortcuts import HttpResponse, get_object_or_404, redirect, render
from django.views.generic import DetailView, RedirectView, TemplateView, View
from django.views.generic.base import ContextMixin
from notifications.models import Notification

from iconolab.forms.annotations import AnnotationRevisionForm
from iconolab.forms.bookmarks import BookmarkForm
from iconolab.models import (Annotation, AnnotationRevision, Bookmark,
                             BookmarkCategory, Collection, Folder,
                             IconolabComment, Image, Item, MetaCategory,
                             MetaCategoryInfo, Tag, TaggingInfo)
from iconolab.serializers import AnnotationRevisionSerializer

logger = logging.getLogger(__name__)

class GlobalHomepageView(View):
    """
        View for the opening page of Iconolab.
    """
    def get(self, request, *args, **kwargs):
        """
            Template is iconolab/home.html

            Context variables provided to the template are:
                collections_primary: list of collections to display as big images
                collections_secondary: list of collections to display as small links at the bottom
                homepage = True: used to pass checks in the partials/header.html
                    template to adjust the navbar to the homepage
        """
        context = {}
        context['collections_primary'] = Collection.objects.filter(show_image_on_home=True).all()
        context['collections_secondary'] = Collection.objects.filter(show_image_on_home=False).all()
        context['latest_annotations'] = Annotation.objects.order_by("-created").all()[:5]

        # Best contributors
        count_contributions = Annotation.objects.all()\
            .values('author').annotate(contributions=Count('author')).order_by('-contributions')[:10]
        best_authors = { u.id: u for u in User.objects.filter(id__in=[cc['author'] for cc in count_contributions])}
        context['best_contributors'] = [
            {'author': best_authors[cc['author']], 'contributions': cc['contributions']}
            for cc in count_contributions
        ]

        # Most accurate tags (tags with accuracy >= 4)
        # SELECT ti.tag_id, ar.title, COUNT(DISTINCT(a.id)) AS cnt
        # FROM iconolab_tagginginfo ti
        # JOIN iconolab_annotationrevision ar ON ti.revision_id = ar.id
        # JOIN iconolab_annotation a ON ar.annotation_id = a.id
        # WHERE ti.accuracy >= 4
        # GROUP BY ti.tag_id
        # ORDER BY cnt desc
        rows = TaggingInfo.objects\
            .filter(accuracy__gte=4)\
            .values('tag')\
            .annotate(annotation_count=Count('revision__annotation', distinct=True))\
            .order_by('-annotation_count')\
            .all()[:10]

        best_tags = {t.id: t for t in Tag.objects.filter(id__in=[r['tag'] for r in rows])}
        context['most_accurate_tags'] = [
            {'tag': best_tags[r['tag']], 'annotation_count': r['annotation_count']}
            for r in rows
        ]

        context['contact'] = settings.CONTACT_EMAIL
        context['homepage'] = True
        return render(request, 'iconolab/home.html', context)

class TestView(View):
    template_name = 'iconolab/compare.html'

    def get(self, request, *args, **kwargs):
        return render(request, self.template_name)

# Class with check_kwargs method to fetch objects from database depending on what level in the app we're currently at
class IconolabObjectView(object):
    """
        Superclass that defines method used in all object display views.
    """
    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():
            try:
                objects_tuple += (Collection.objects.prefetch_related(
                    Prefetch(
                        'folders',
                        Folder.objects.annotate(items_nb=Count('item')).order_by('name')
                    )
                ).get(name=kwargs.get('collection_name')),)
            except (ValueError, Collection.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        if 'item_guid' in kwargs.keys():
            try:
                objects_tuple += (Item.objects.prefetch_related('images', '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():
            try:
                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():
            try:
                objects_tuple += (Annotation.objects.prefetch_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():
            try:
                objects_tuple += (AnnotationRevision.objects.prefetch_related('parent_revision').get(revision_guid=kwargs.get('revision_guid')),)
            except (ValueError, AnnotationRevision.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        return True, objects_tuple

    def get_pagination_data(self, list_to_paginate, page, perpage, adjacent_pages_count, perpage_range=[5, 10, 25, 100], trailing_qarg=""):
        """
            Takes a queryset or a list and returns a dict with pagination data for display purposes

            Dict will be of the format:
            {
                page: the page to load (integer)
                perpage_range: a list of the page links to display (list of integers)
                perpage: the item count per page (integer)
                perpage_range: a list of the perpage values to display next to the page list (list of integers)
                trailing_qarg: optional trailing qarg for the paginations links (used in collection home to remember the state of each list between page loads) (string)
                list: the item list to display (list of objects)
                show_first: used in template to display links, will be True if 1 is not in page_range
                show_last: used in template to display links, will be True if page_count is not in page_range
                ellipsis_first: used in template to display links, will be True if page_range starts at 3 or more
                ellipsis_last: used in template to display links, will be True if page_range ends at last_page - 2 or less

            }
        """
        pagination_data = {}
        pagination_data["page"] = page
        pagination_data["perpage"] = perpage
        pagination_data["perpage_range"] = perpage_range
        pagination_data["trailing_qarg"] = trailing_qarg
        paginator = Paginator(list_to_paginate, perpage)
        try:
            pagination_data["list"] = paginator.page(page)
        except PageNotAnInteger:
            pagination_data["list"] = paginator.page(1)
        except EmptyPage:
            pagination_data["list"] = paginator.page(paginator.num_pages)
        pagination_data["page_range"] = [
            n for n in \
            range(page - adjacent_pages_count, page + adjacent_pages_count + 1) \
                if n > 0 and n <= paginator.num_pages
        ]
        pagination_data["show_first"] = page - adjacent_pages_count > 1
        pagination_data["ellipsis_first"] = pagination_data["show_first"] and (page - adjacent_pages_count != 2)
        pagination_data["show_last"] = page + adjacent_pages_count < paginator.num_pages
        pagination_data["ellipsis_last"] = pagination_data["show_last"] and (page + adjacent_pages_count != paginator.num_pages - 1)
        return pagination_data

class CollectionHomepageView(View, ContextMixin, IconolabObjectView):
    """
        View that displays a collection and four panels to show relevant paginated lists for collection:
        * item lists
        * annotations ordered by creation date
        * annotations ordered by revisions count
        * annotations where a metacategory that notifies contributors was called
    """
    def get(self, request, *args, **kwargs):
        """
            Template is iconolab/collection_home.html

            Url args are:
                - collection_name: 'name' attribute of the requested collection

            Queryargs understood by the view are:
                - show : panel that will be shown on page load, one of ['items', 'recent', 'revised', 'contributions'], default to "items"
                - items_page : item list page to load
                - items_perpage : item count per page
                - recent_page : recent annotations list page to load
                - recent_perpage : recent annotations count per page
                - revised_page : most revised annotations list page to load
                - revised_perpage : most revised annotations count per page
                - contributions_page : annotations with the most contribution calls list page to load
                - contributions_perpage : annotations with the most contribution calls count per page for item list

            Context variables provided to the template are:
                - collection: the collection object for the requested collection
                - collection_name : the collection_name url arg
                - items_pagination_data: pagination data dict in the format of the IconolabObjectView.get_pagination_data() method for the items list
                - recent_pagination_data: pagination data dict in the format of the IconolabObjectView.get_pagination_data() method for the recent annotations list
                - revised_pagination_data: pagination data dict in the format of the IconolabObjectView.get_pagination_data() method for the revised annotations list
                - contributions_pagination_data: pagination data dict in the format of the IconolabObjectView.get_pagination_data() method for the contribution calls annotations list
        """

        success, result = self.check_kwargs(kwargs)
        if success:
            (collection,) = result
        else:
            return result(request)
        context = super(CollectionHomepageView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['collection'] = collection

        # get Pagination and navigation query args
        try:
            items_page = int(request.GET.get('items_page', '1'))
        except ValueError:
            items_page = 1
        try:
            items_per_page = int(request.GET.get('items_perpage', '12'))
        except ValueError:
            items_per_page = 12

        try:
            recent_page = int(request.GET.get('recent_page', '1'))
        except ValueError:
            recent_page = 1
        try:
            recent_per_page = int(request.GET.get('recent_perpage', '10'))
        except ValueError:
            recent_per_page = 10

        try:
            revised_page = int(request.GET.get('revised_page', '1'))
        except ValueError:
            revised_page = 1
        try:
            revised_per_page = int(request.GET.get('revised_perpage', '10'))
        except ValueError:
            revised_per_page = 10

        try:
            contributions_page = int(request.GET.get('contributions_page', '1'))
        except ValueError:
            contributions_page = 1
        try:
            contributions_per_page = int(request.GET.get('contributions_perpage', '10'))
        except ValueError:
            contributions_per_page = 10

        active_list = request.GET.get('show', 'items')
        if active_list not in ['items', 'recent', 'revised', 'contributions']:
            active_list = 'items'
        context["active_list"] = active_list


        # Pagination values
        adjacent_pages_count = 2

        # Paginated objects list
        items_list = Item.objects.filter(collection=collection).prefetch_related('images', 'images__stats').select_related('collection').order_by('metadatas__natural_key')

        folder = request.GET.get('folder', None)

        if folder is not None:
            items_list = items_list.filter(folders__folder_guid=folder)
            context['folder_name'] = Folder.objects.get(folder_guid=folder).name

        context['folder_guid'] = folder

        items_pagination_args = '&'.join([
            "recent_page="+str(recent_page),
            "recent_perpage="+str(recent_per_page),
            "revised_page="+str(revised_page),
            "revised_perpage="+str(revised_per_page),
            "contributions_page="+str(contributions_page),
            "contributions_perpage="+str(contributions_per_page)
        ])

        if folder is not None:
            items_pagination_args += "&folder=" + folder

        context["items_pagination_data"] = self.get_pagination_data(
            items_list,
            items_page,
            items_per_page,
            adjacent_pages_count,
            perpage_range=[6, 12, 48, 192],
            trailing_qarg='&'+items_pagination_args
        )

        # Paginated recent annotations list
        recent_annotations = Annotation.objects.filter(image__item__collection__name=collection.name).select_related('image', 'author').prefetch_related(
            'current_revision',
            'stats',
            'stats__contributors'
        ).order_by('-current_revision__created')
        context["recent_pagination_data"] = self.get_pagination_data(
            recent_annotations,
            recent_page,
            recent_per_page,
            adjacent_pages_count,
            trailing_qarg="&items_page="+str(items_page)
            +"&items_perpage="+str(items_per_page)
            +"&revised_page="+str(revised_page)
            +"&revised_perpage="+str(revised_per_page)
            +"&contributions_page="+str(contributions_page)
            +"&contributions_perpage="+str(contributions_per_page)
        )

        # Paginated revised annotations list
        revised_annotations = Annotation.objects.filter(image__item__collection__name=collection.name).select_related('image', 'author').prefetch_related(
            'current_revision',
            'stats',
            'stats__contributors'
        ).annotate(revision_count=Count('revisions')).order_by('-revision_count')
        context["revised_pagination_data"] = self.get_pagination_data(
            revised_annotations,
            revised_page,
            revised_per_page,
            adjacent_pages_count,
            trailing_qarg="&items_page="+str(items_page)
            +"&items_perpage="+str(items_per_page)
            +"&recent_page="+str(recent_page)
            +"&recent_perpage="+str(recent_per_page)
            +"&contributions_page="+str(contributions_page)
            +"&contributions_perpage="+str(contributions_per_page)
        )

        # Paginated contribution calls annotation list
        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)\
                .select_related('image', 'current_revision')\
                .prefetch_related(
                    'stats',
                    'stats__contributors',
                    Prefetch(
                        'current_revision__tagginginfo_set',
                        queryset=TaggingInfo.objects.select_related('tag')
                    )
                )\
                .all()
        collection_ann_dict = dict([(str(annotation.id), annotation) for annotation in collection_annotations])
        contributions_annotations = [collection_ann_dict[id] for id in contrib_calls_annotations_ids]
        context["contributions_pagination_data"] = self.get_pagination_data(
            contributions_annotations,
            contributions_page,
            contributions_per_page,
            adjacent_pages_count,
            trailing_qarg="&items_page="+str(items_page)
            +"&items_perpage="+str(items_per_page)
            +"&recent_page="+str(recent_page)
            +"&recent_perpage="+str(recent_per_page)
            +"&revised_page="+str(revised_page)
            +"&revised_perpage="+str(revised_per_page)
        )

        context['bookmark_form'] = BookmarkForm(user=request.user)

        return render(request, 'iconolab/collection_home.html', context)



class ShowItemView(View, ContextMixin, IconolabObjectView):
    """
        View that displays informations on an item with associated metadatas and stats. Also displays images and annotation list for each image.
    """
    def get(self, request, *args, **kwargs):
        """
            Template is iconolab/item_detail.html

            Url args are:
                - collection_name : name of the collection
                - item_guid: 'item_guid' attribute of the requested item

            Queryargs understood by the view are:
                - show: image_guid for the image to show on load
                - page: annotation list page on load for displayed image
                - perpage: annotation count per page on load for displayed image

            Context variables provided to the template are:
                - collection_name : the collection_name url arg
                - item_guid: the item_guid url arg
                - collection: the collection object for the requested collection
                - item: the item object for the requested item
                - display_image: the image_guid for the image to display on load
                - images: a list of dict for the item images data in the format:
                    {
                        'obj': the image object,
                        'annotations': the list of annotations on that image
                    }
        """

        success, result = self.check_kwargs(kwargs)

        if success:
            (collection, item) = result
        else:
            return result(request)

        context = super(ShowItemView, self).get_context_data(**kwargs)
        image_guid_to_display = request.GET.get("show", str(item.images.first().image_guid))
        if image_guid_to_display not in [str(guid) for guid in item.images.all().values_list("image_guid", flat=True)]:
            image_guid_to_display = str(item.images.first().image_guid)
        context['display_image'] = image_guid_to_display
        try:
            displayed_annotations_page = int(request.GET.get('page', '1'))
        except ValueError:
            displayed_annotations_page = 1
        try:
            displayed_annotations_per_page = int(request.GET.get('perpage', '10'))
        except ValueError:
            displayed_annotations_per_page = 10

        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['item_guid'] = self.kwargs.get('image_guid', '')
        context['collection'] = collection
        context['item'] = item
        context['images'] = []
        for image in item.images.all():
            if str(image.image_guid) == image_guid_to_display:
                page = displayed_annotations_page
                per_page = displayed_annotations_per_page
            else:
                page = 1
                per_page = 10
            annotations_paginator = Paginator(image.annotations.all().order_by('created'), per_page)
            try:
                annotations = annotations_paginator.page(page)
            except PageNotAnInteger:
                annotations = annotations_paginator.page(1)
            except EmptyPage:
                annotations = annotations_paginator.page(recent_paginator.num_pages)
            context['images'].append({
                'obj' : image,
                'annotations': annotations
            })
            image.stats.views_count += 1
            image.stats.save()
        return render(request, 'iconolab/detail_item.html', context)

class ShowImageView(View, ContextMixin, IconolabObjectView):
    """
        View that only displays an image and the associated annotations
    """
    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image) = result
        else:
            return result(request)
        context = super(ShowImageView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['collection'] = collection
        context['image'] = image
        context['item'] = image.item
        context['form'] = AnnotationRevisionForm()
        return render(request, 'iconolab/detail_image.html', context)

class BookmarkImageView(View, ContextMixin, IconolabObjectView):
    def post(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image) = result
        else:
            return result(request)

        context = super(BookmarkImageView, self).get_context_data(**kwargs)

        bookmark_form = BookmarkForm(request.POST, user=request.user)
        if bookmark_form.is_valid():
            bookmark = Bookmark(
                image=image,
                category=bookmark_form.cleaned_data['category']
            )
            bookmark.save()

        context['bookmark_form'] = bookmark_form

        redirect_url = reverse('collection_home', kwargs={'collection_name': self.kwargs.get('collection_name', '')})

        return redirect(redirect_url)

class CreateAnnotationView(View, ContextMixin, IconolabObjectView):
    """
        View that displays annotation forms and handles annotation creation
    """
    def get_context_data(self, **kwargs):
        context = super(CreateAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image,) = result
        else:
            return result(request)
        annotation_form = AnnotationRevisionForm()
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['tags_data'] = '[]'
        return render(request, 'iconolab/change_annotation.html', context)

    def post(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_form = AnnotationRevisionForm(request.POST)
        if annotation_form.is_valid():
            author = request.user
            title = annotation_form.cleaned_data['title']
            description = annotation_form.cleaned_data['description']
            fragment = annotation_form.cleaned_data['fragment']
            tags_json = annotation_form.cleaned_data['tags']
            new_annotation = Annotation.objects.create_annotation(author, image, title=title, description=description, fragment=fragment, tags_json=tags_json)
            redirect_url = reverse('image_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid})
            return redirect(redirect_url + '#' + str(new_annotation.annotation_guid))
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['tags_data'] = '[]'
        return render(request, 'iconolab/change_annotation.html', context)

class ShowAnnotationView(View, ContextMixin, IconolabObjectView):
    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation) = result
        else:
            return result(request)
        context = super(ShowAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['collection'] = collection
        context['image'] = image
        context['item'] = image.item
        context['annotation'] = annotation
        context['form'] = AnnotationRevisionForm()
        return render(request, 'iconolab/detail_image.html', context)

class ShowAnnotationViewOld(View, ContextMixin, IconolabObjectView):
    """
        View that show a given annotation with the corresponding data, links to
        submit new revisions and the paginated comments thread.
    """


    def get_context_data(self, **kwargs):
        context = super(ShowAnnotationViewOld, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        """
            Template is iconolab/detail_annotations.html

            Url args are:
                - collection_name: 'name' attribute of the requested collection
                - item_guid: 'item_guid' attribute of the requested item
                - annotation_guid: 'annotation_guid' attribute of the requested annotation

            Queryargs understood by the view are:
                - page: comment thread page on load
                - perpage: comment count per page on load

            Context variables provided to the template are:
                - collection: the collection object for the requested collection
                - image: the image object for the requested image
                - annotation: the annotation object for the requested annotation
                - tags_data: a json string describing tags for the annotation current revision
                - comments: the paginated comments list for the annotation according page and perpage queryargs
                - notification_comments_ids: the ids of the comments that are referenced by a notification for the authenticated user; This allows
                us to highlight comments that triggered a notification in the page
        """
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)
        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        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')
        paginator = Paginator(full_comments_list, per_page)
        try:
            comments_list = paginator.page(page)
        except PageNotAnInteger:
            comments_list = paginator.page(1)
        except EmptyPage:
            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()
        annotation.stats.views_count += 1
        annotation.stats.save()
        return render(request, 'iconolab/detail_annotation.html', context)

class ShowRevisionsView(View, ContextMixin, IconolabObjectView):
    def get_context_data(self, **kwargs):
        context = super(ShowRevisionsView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)

        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation

        context['revisions'] = AnnotationRevision.objects.filter(annotation=annotation).order_by('-created')

        return render(request, 'iconolab/annotation_revisions.html', context)

class ReadonlyAnnotationView(View, ContextMixin, IconolabObjectView):
    """
        Same view as ShowAnnotationView but without the comments and links to the forms
    """
    def get_context_data(self, **kwargs):
        context = super(ReadonlyAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        """
            Exactly the same as ShowAnnotationView but without all the data around comments
        """
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)
        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        context['tags_data'] = annotation.current_revision.get_tags_json()

        image.stats.views_count += 1
        image.stats.save()
        annotation.stats.views_count += 1
        annotation.stats.save()
        return render(request, 'iconolab/detail_annotation_readonly.html', context)

class EditAnnotationView(View, ContextMixin, IconolabObjectView):
    """
        View that handles displaying the edition form and editing an annotation
    """
    def get_context_data(self, **kwargs):
        context = super(EditAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)
        annotation_form = AnnotationRevisionForm(instance=annotation.current_revision)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['annotation'] = annotation
        context['form'] = annotation_form
        context['tags_data'] = annotation.current_revision.get_tags_json()
        return render(request, 'iconolab/change_annotation.html', context)

    def post(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_guid = kwargs['annotation_guid']
        annotation_form = AnnotationRevisionForm(request.POST)
        if annotation_form.is_valid():
            revision_author = request.user
            revision_title = annotation_form.cleaned_data['title']
            revision_description = annotation_form.cleaned_data['description']
            revision_fragment = annotation_form.cleaned_data['fragment']
            revision_tags_json = annotation_form.cleaned_data['tags']
            new_revision = annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, revision_tags_json)

            if (annotation.author != revision_author):
                messages.add_message(request, messages.INFO, "Votre modification a été prise en compte. Le créateur de l'annotation a été notifié.")

            redirect_url = reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': str(annotation.annotation_guid)})
            return redirect(redirect_url)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['annotation'] = annotation
        context['tags_data'] = annotation.current_revision.get_tags_json()
        return render(request, 'iconolab/change_annotation.html', context)


class ShowRevisionView(View, ContextMixin, IconolabObjectView):
    """
        View that displays a given revision with its associated data and comment
    """
    def get_context_data(self, **kwargs):
        context = super(ShowRevisionView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        context['revision_guid'] = self.kwargs.get('revision_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        """
            Template is iconolab/detail_annotations.html

            Url args are:
                - collection_name: 'name' attribute of the requested collection
                - item_guid: 'item_guid' attribute of the requested item
                - annotation_guid: 'annotation_guid' attribute of the requested annotation
                - revision_guid: 'revision_guid' attribute of the requested revision

            Context variables provided to the template are:
                - collection: the collection object for the requested collection
                - image: the image object for the requested image
                - annotation: the annotation object for the requested annotation
                - revision: the revision object for the requested annotation
                - tags_data: a json string describing tags for the annotation current revision
                - comment: the comment that was posted alongside the revision
                - notified_revision: if True, the revision is linked from one or more unread notifications for the
                current user, allowing us to highlight it in the template.
        """
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision,) = result
        else:
            return result(request)
        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        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:
            ann_author_notified = Notification.objects.filter(
                    recipient=request.user,
                    action_object_content_type__app_label='iconolab',
                    action_object_content_type__model='annotationrevision',
                    action_object_object_id=revision.id,
                    target_content_type__app_label='iconolab',
                    target_content_type__model='annotation',
                    target_object_id=annotation.id
                ).unread()
            if ann_author_notified:
                ann_author_notified.first().mark_as_read()
                context['notified_revision'] = True
        if request.user.is_authenticated and revision.author == request.user:
            rev_author_notified = Notification.objects.filter(
                    recipient=request.user,
                    action_object_content_type__app_label='iconolab',
                    action_object_content_type__model='annotationrevision',
                    action_object_object_id=revision.id,
                    target_content_type__app_label='iconolab',
                    target_content_type__model='annotation',
                    target_object_id=annotation.id
                ).unread()
            if rev_author_notified:
                rev_author_notified.first().mark_as_read()
                context['notified_revision'] = True
        return render(request, 'iconolab/detail_revision.html', context)


class MergeProposalView(View, ContextMixin, IconolabObjectView):
    """
        View that displays the merge form, used when a user wants to "study" a revision because it was submitted from an older revision than the current revision (thus
        the two revisions don't have the same parents and there is a conflict)
    """
    def get_context_data(self, **kwargs):
        context = super(MergeProposalView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        context['revision_guid'] = self.kwargs.get('revision_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision,) = result
        else:
            return result(request)
        # Only show merge form if there is a revision to merge AND the current user is the annotation author
        if revision.state != AnnotationRevision.AWAITING or request.user != annotation.author:
            return RedirectView.as_view(
                url=reverse('revision_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid,
                        'revision_guid': revision.revision_guid
                    }
                )
            )(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:
            annotation.validate_existing_revision(revision)
            return RedirectView.as_view(
                url=reverse('annotation_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid
                    }
                )
            )(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']:
            annotation.reject_existing_revision(revision)
            return RedirectView.as_view(
                url=reverse('annotation_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid
                    }
                )
            )(request)

        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        # Proposal data
        context['proposal_revision'] = revision
        context['proposal_tags_data'] = revision.get_tags_json()
        context['proposal_comment'] = revision.creation_comment.first()
        # Parent data
        context['parent_revision'] = revision.parent_revision
        context['parent_tags_data'] = revision.parent_revision.get_tags_json()
        context['parent_comment'] = revision.parent_revision.creation_comment.first()
        # Current data
        context['current_revision'] = annotation.current_revision
        context['current_tags_data'] = annotation.current_revision.get_tags_json()
        context['current_comment'] = annotation.current_revision.creation_comment.first()

        merge_form = AnnotationRevisionForm(instance=revision)
        context['merge_form'] = merge_form
        return render(request, 'iconolab/merge_revision.html', context)

    def post(self, request, *args, **kwargs):
        # Handle merge form submit here
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_guid = kwargs['annotation_guid']
        revision_guid = kwargs['revision_guid']

        merge_revision_form = AnnotationRevisionForm(request.POST)
        if merge_revision_form.is_valid():
            revision_title = merge_revision_form.cleaned_data['title']
            revision_description = merge_revision_form.cleaned_data['description']
            revision_fragment = merge_revision_form.cleaned_data['fragment']
            revision_tags_json = merge_revision_form.cleaned_data['tags']
            new_revision = annotation.merge_existing_revision(revision_title, revision_description, revision_fragment, revision_tags_json, revision)
            revision_comment = merge_revision_form.cleaned_data['comment']
            comment = IconolabComment.objects.create(
                comment = revision_comment,
                revision = new_revision,
                content_type = ContentType.objects.get(app_label='iconolab', model='annotation'),
                content_object = annotation,
                site = Site.objects.get(id=settings.SITE_ID),
                object_pk = annotation.id,
                user = request.user,
                user_name = request.user.username
            )
            return RedirectView.as_view(url=reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': annotation_guid}))(request)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['merge_form'] = merge_revision_form
        context['annotation'] = annotation
        # Proposal data
        context['proposal_revision'] = revision
        context['proposal_tags_data'] = revision.get_tags_json()
        context['proposal_comment'] = revision.creation_comment.first()
        # Parent data
        context['parent_revision'] = revision.parent_revision
        context['parent_tags_data'] = revision.parent_revision.get_tags_json()
        context['parent_comment'] = revision.parent_revision.creation_comment.first()
        # Current data
        context['current_revision'] = annotation.current_revision
        context['current_tags_data'] = annotation.current_revision.get_tags_json()
        context['current_comment'] = annotation.current_revision.creation_comment.first()
        return render(request, 'iconolab/merge_revision.html', context)