src/iconolab/views/objects.py
author Alexandre Segura <mex.zktk@gmail.com>
Thu, 09 Mar 2017 12:11:46 +0100
changeset 416 5daa15b87404
parent 407 74e0a5ea614a
child 461 4701ee4556e3
permissions -rw-r--r--
Introduce folders.

from django.shortcuts import HttpResponse, get_object_or_404, render, redirect
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
from django.views.generic.base import ContextMixin
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.conf import settings
from notifications.models import Notification
from iconolab.models import Annotation, AnnotationRevision, Collection, Item, Image, IconolabComment, MetaCategory, MetaCategoryInfo
from iconolab.forms.annotations import AnnotationRevisionForm
from iconolab.serializers import AnnotationRevisionSerializer
import logging

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['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('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():
            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 = collection.items.order_by("metadatas__inventory_number")

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

        if folder is not None:
            folder = int(folder)
            items_list = items_list.filter(folders__id=folder)

        context['folder_id'] = folder

        context["items_pagination_data"] = self.get_pagination_data(
            items_list.all(),
            items_page,
            items_per_page,
            adjacent_pages_count,
            perpage_range=[6, 12, 48, 192],
            trailing_qarg="&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)
        )

        # Paginated recent annotations list
        recent_annotations = Annotation.objects.filter(image__item__collection__name=collection.name).prefetch_related(
            'current_revision',
            'stats'
        ).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).prefetch_related(
            'current_revision',
            'stats'
        ).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).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)
        )

        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(), 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 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):
    """
        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(ShowAnnotationView, 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 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)
            redirect_url = reverse('image_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid})
            return redirect(redirect_url + '#' + str(annotation.annotation_guid))
        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)