--- a/src/iconolab/models.py Fri Dec 16 13:24:55 2016 +0100
+++ b/src/iconolab/models.py Wed Jan 18 14:11:30 2017 +0100
@@ -6,7 +6,12 @@
from django_comments_xtd.models import XtdComment
from django.utils.text import slugify
import iconolab.signals.handlers as iconolab_signals
-import uuid, json, re, requests, urllib, logging
+import uuid
+import json
+import re
+import requests
+import urllib
+import logging
logger = logging.getLogger(__name__)
@@ -14,19 +19,24 @@
class Collection(models.Model):
"""
Collection objects are the thematic item repositories in Iconolab
-
+
name: the name displayed in the url and also used to identify the collection
verbose_name: the name displayed in the text of the pages
- description: the short description of the collection that will be displayed by default in pages
- complete_description: the complete description that will be shown with a "view more" button/link
- image/height/width: the collection image that will be shown in the collection description
- show_image_on_home: if True, the collection will appear by default on the homepage as one of the bigger images
+ description: the short description of the collection that will be
+ displayed by default in pages
+ complete_description: the complete description that will be shown
+ with a "view more" button/link
+ image/height/width: the collection image that will be shown in the
+ collection description
+ show_image_on_home: if True, the collection will appear by default
+ on the homepage as one of the bigger images
"""
name = models.SlugField(max_length=50, unique=True)
verbose_name = models.CharField(max_length=50, null=True, blank=True)
description = models.TextField(null=True, blank=True, default="")
complete_description = models.TextField(null=True, blank=True, default="")
- image = models.ImageField(upload_to='uploads/', height_field='height', width_field='width', null=True, blank=True)
+ image = models.ImageField(
+ upload_to='uploads/', height_field='height', width_field='width', null=True, blank=True)
height = models.IntegerField(null=True, blank=True)
width = models.IntegerField(null=True, blank=True)
show_image_on_home = models.BooleanField(default=False)
@@ -37,21 +47,23 @@
class Item(models.Model):
"""
- Item objects belong to a collection, are linked to a metadata item, and to one or more images
+ Item objects belong to a collection, are linked to a metadata item, and
+ to one or more images
"""
collection = models.ForeignKey(Collection, related_name="items")
item_guid = models.UUIDField(default=uuid.uuid4, editable=False)
-
+
def __str__(self):
- return str(self.item_guid)+":from:"+self.collection.name
-
+ return str(self.item_guid) + ":from:" + self.collection.name
+
@property
def images_sorted_by_name(self):
return self.images.order_by("-name").all()
-
+
+
class ItemMetadata(models.Model):
"""
- Metadata object for the item class. Each field represents what we can import from the provided .csv files
+ Metadata object for the item class. Each field represents what we can import from the provided .csv files
"""
item = models.OneToOneField('Item', related_name='metadatas')
authors = models.CharField(max_length=255, default="")
@@ -67,35 +79,37 @@
photo_credits = models.CharField(max_length=255, default="")
inventory_number = models.CharField(max_length=255, default="")
joconde_ref = models.CharField(max_length=255, default="")
-
+
@property
def get_joconde_url(self):
- return settings.JOCONDE_NOTICE_BASE_URL+self.joconde_ref.rjust(11, '0')
-
+ return settings.JOCONDE_NOTICE_BASE_URL + self.joconde_ref.rjust(11, '0')
+
def __str__(self):
- return "metadatas:for:"+str(self.item.item_guid)
+ return "metadatas:for:" + str(self.item.item_guid)
class Image(models.Model):
"""
Each image object is linked to one item, users can create annotations on images
"""
-
+
image_guid = models.UUIDField(default=uuid.uuid4, editable=False)
name = models.CharField(max_length=200)
- media = models.ImageField(upload_to='uploads/', height_field='height', width_field='width')
- item = models.ForeignKey('Item', related_name='images', null=True, blank=True)
+ media = models.ImageField(upload_to='uploads/',
+ height_field='height', width_field='width')
+ item = models.ForeignKey(
+ 'Item', related_name='images', null=True, blank=True)
height = models.IntegerField(null=False, blank=False)
width = models.IntegerField(null=False, blank=False)
created = models.DateTimeField(auto_now_add=True, null=True)
-
+
def __str__(self):
- return str(self.image_guid)+":"+self.name
-
+ return str(self.image_guid) + ":" + self.name
+
@property
def wh_ratio(self):
return self.width / self.height
-
+
@property
def collection(self):
return self.item.collection.name
@@ -111,7 +125,7 @@
@property
def school(self):
return self.item.metadatas.school
-
+
@property
def designation(self):
return self.item.metadatas.designation
@@ -119,7 +133,7 @@
@property
def datation(self):
return self.item.metadatas.datation
-
+
@property
def technics(self):
return self.item.metadatas.technics
@@ -132,8 +146,10 @@
def tag_labels(self):
tag_list = []
for annotation in self.annotations.all():
- revision_tags = json.loads(annotation.current_revision.get_tags_json())
- tag_list += [tag_infos['tag_label'] for tag_infos in revision_tags if tag_infos.get('tag_label') is not None] #deal with
+ revision_tags = json.loads(
+ annotation.current_revision.get_tags_json())
+ tag_list += [tag_infos['tag_label']
+ for tag_infos in revision_tags if tag_infos.get('tag_label') is not None] # deal with
return tag_list
@@ -141,20 +157,24 @@
"""
Stats objects for a given image, keep count of several values to be displayed in image and item pages
"""
- image = models.OneToOneField('Image', related_name='stats', blank=False, null=False)
+ image = models.OneToOneField(
+ 'Image', related_name='stats', blank=False, null=False)
views_count = models.IntegerField(blank=True, null=True, default=0)
annotations_count = models.IntegerField(blank=True, null=True, default=0)
- submitted_revisions_count = models.IntegerField(blank=True, null=True, default=0)
+ submitted_revisions_count = models.IntegerField(
+ blank=True, null=True, default=0)
comments_count = models.IntegerField(blank=True, null=True, default=0)
- folders_inclusion_count = models.IntegerField(blank=True, null=True, default=0)
+ folders_inclusion_count = models.IntegerField(
+ blank=True, null=True, default=0)
tag_count = models.IntegerField(blank=True, null=True, default=0)
-
+
def __str__(self):
- return "stats:for:"+str(self.image.image_guid)
-
+ return "stats:for:" + str(self.image.image_guid)
+
def set_tags_stats(self):
- self.tag_count = Tag.objects.filter(tagginginfo__revision__annotation__image = self.image).distinct().count()
-
+ self.tag_count = Tag.objects.filter(
+ tagginginfo__revision__annotation__image=self.image).distinct().count()
+
@transaction.atomic
def update_stats(self):
self.annotations_count = 0
@@ -168,18 +188,19 @@
for annotation in image_annotations.all():
annotation_revisions = annotation.revisions
self.submitted_revisions_count += annotation_revisions.count()
-
+
self.comments_count += XtdComment.objects.for_app_models("iconolab.annotation").filter(
- object_pk = annotation.pk,
+ object_pk=annotation.pk,
).count()
# tag_count
- self.tag_count = Tag.objects.filter(tagginginfo__revision__annotation__image = self.image).distinct().count()
+ self.tag_count = Tag.objects.filter(
+ tagginginfo__revision__annotation__image=self.image).distinct().count()
self.save()
class AnnotationManager(models.Manager):
"""
- Manager class for annotation, it handles annotation creation (with initial revision creation, and
+ Manager class for annotation, it handles annotation creation (with initial revision creation, and
has methods to get a list of annotation commented for a given user, and a list of annotations contributed for a
given user
"""
@@ -190,14 +211,14 @@
"""
# Create annotation object
new_annotation = Annotation(
- image=image,
+ image=image,
author=author
)
new_annotation.save()
-
+
# Create initial revision
initial_revision = AnnotationRevision(
- annotation=new_annotation,
+ annotation=new_annotation,
author=author,
title=title,
description=description,
@@ -206,33 +227,36 @@
)
initial_revision.save()
initial_revision.set_tags(tags_json)
-
+
# Create stats object
new_annotation_stats = AnnotationStats(annotation=new_annotation)
new_annotation_stats.set_tags_stats()
new_annotation_stats.save()
-
+
# Link everything to parent
new_annotation.current_revision = initial_revision
new_annotation.stats = new_annotation_stats
new_annotation.save()
- iconolab_signals.revision_created.send(sender=AnnotationRevision, instance=initial_revision)
+ iconolab_signals.revision_created.send(
+ sender=AnnotationRevision, instance=initial_revision)
return new_annotation
@transaction.atomic
def get_annotations_contributed_for_user(self, user):
"""
user is the user whom we want to get the contributed annotations
-
- Returns the list of all the annotations on which the user submitted a revision but did not create the annotation
+
+ Returns the list of all the annotations on which the user submitted
+ a revision but did not create the annotation
List of dict in the format:
-
+
{
"annotation_obj": annotation object,
"revisions_count": revisions count for user
"awaiting_count": awaiting revisions for user on this annotation
"accepted_count": accepted revisions for user
- "latest_submitted_revision": date of the latest submitted revision from user on annotation
+ "latest_submitted_revision": date of the latest submitted revision
+ from user on annotation
}
"""
latest_revision_on_annotations = []
@@ -243,13 +267,15 @@
'image__item',
'image__item__collection').distinct()
for annotation in user_contributed_annotations.all():
- latest_revision_on_annotations.append(annotation.revisions.filter(author=user).latest(field_name="created"))
+ latest_revision_on_annotations.append(
+ annotation.revisions.filter(author=user).latest(field_name="created"))
contributed_list = []
if latest_revision_on_annotations:
- latest_revision_on_annotations.sort(key=lambda item:item.created, reverse=True)
- contributed_list= [
+ latest_revision_on_annotations.sort(
+ key=lambda item: item.created, reverse=True)
+ contributed_list = [
{
- "annotation_obj": revision.annotation,
+ "annotation_obj": revision.annotation,
"revisions_count": revision.annotation.revisions.filter(author=user).count(),
"awaiting_count": revision.annotation.revisions.filter(author=user, state=AnnotationRevision.AWAITING).count(),
"accepted_count": revision.annotation.revisions.filter(author=user, state=AnnotationRevision.ACCEPTED).count(),
@@ -259,33 +285,36 @@
]
logger.debug(contributed_list)
return contributed_list
-
+
@transaction.atomic
def get_annotations_commented_for_user(self, user, ignore_revisions_comments=True):
"""
user is the user for which we want to get the commented annotations
ignore_revisions_comment allows to filter comments that are associated with a revision
-
-
+
+
Returns a list of all annotations on which a given user commented with user-comments-related data
List of dict in the format:
-
+
{
"annotation_obj": annotation object,
"comment_count": comment count for user
"latest_comment_date": date of the latest comment from user on annotation
}
"""
- user_comments = IconolabComment.objects.filter(user=user, content_type__app_label='iconolab', content_type__model='annotation').order_by('-submit_date')
+ user_comments = IconolabComment.objects.filter(
+ user=user, content_type__app_label='iconolab', content_type__model='annotation').order_by('-submit_date')
if ignore_revisions_comments:
logger.debug(user_comments.count())
user_comments = user_comments.filter(revision__isnull=True)
logger.debug(user_comments.count())
- all_user_comments_data = [(comment.object_pk, comment.submit_date) for comment in user_comments]
+ all_user_comments_data = [
+ (comment.object_pk, comment.submit_date) for comment in user_comments]
unique_ordered_comments_data = []
- for (id, submit_date) in all_user_comments_data:
+ for (id, submit_date) in all_user_comments_data:
if id not in [item["annotation_id"] for item in unique_ordered_comments_data]:
- unique_ordered_comments_data.append({"annotation_id": id, "latest_comment_date": submit_date})
+ unique_ordered_comments_data.append(
+ {"annotation_id": id, "latest_comment_date": submit_date})
commented_annotations = Annotation.objects.filter(id__in=[item["annotation_id"] for item in unique_ordered_comments_data]).prefetch_related(
'current_revision',
'revisions',
@@ -296,11 +325,12 @@
sorted_annotations_list = []
logger.debug(unique_ordered_comments_data)
for comment_data in unique_ordered_comments_data:
- annotation_obj = commented_annotations.get(id=comment_data["annotation_id"])
+ annotation_obj = commented_annotations.get(
+ id=comment_data["annotation_id"])
sorted_annotations_list.append(
{
- "annotation_obj": annotation_obj,
- "comment_count_for_user": user_comments.filter(object_pk=annotation_obj.id).count(),
+ "annotation_obj": annotation_obj,
+ "comment_count_for_user": user_comments.filter(object_pk=annotation_obj.id).count(),
"latest_comment_date": comment_data["latest_comment_date"]
}
)
@@ -309,11 +339,11 @@
class Annotation(models.Model):
"""
- Annotation objects are created on a given image, each annotation have a list of revisions to keep track of its history, the latest revision is the 'current revision'
- that will be displayed by default in most pages.
-
+ Annotation objects are created on a given image, each annotation have a list of revisions to keep track of its history, the latest revision is the 'current revision'
+ that will be displayed by default in most pages.
+
Annotation data (title, description, fragment) is thus stored in the revision.
-
+
Annotations can be considered validated or not depending on the metacategories posted in their comments through the attribute validation_state. Their validation state
can also be overriden and in such case we can use validation_state_overriden attribute to remember it in the model (so for instance if an admin un-validates an annotation
we could block it from being validated again)
@@ -325,55 +355,61 @@
(VALIDATED, 'validated'),
)
annotation_guid = models.UUIDField(default=uuid.uuid4, editable=False)
- image = models.ForeignKey('Image', related_name='annotations', on_delete=models.CASCADE)
- source_revision = models.ForeignKey('AnnotationRevision', related_name='source_related_annotation', blank=True, null=True)
- current_revision = models.OneToOneField('AnnotationRevision', related_name='current_for_annotation', blank=True, null=True)
+ image = models.ForeignKey(
+ 'Image', related_name='annotations', on_delete=models.CASCADE)
+ source_revision = models.ForeignKey(
+ 'AnnotationRevision', related_name='source_related_annotation', blank=True, null=True)
+ current_revision = models.OneToOneField(
+ 'AnnotationRevision', related_name='current_for_annotation', blank=True, null=True)
author = models.ForeignKey(User, null=True)
created = models.DateTimeField(auto_now_add=True, null=True)
- comments = GenericRelation('IconolabComment', content_type_field='content_type_id', object_id_field='object_pk')
- validation_state = models.IntegerField(choices=VALIDATION_STATES, default=UNVALIDATED)
+ comments = GenericRelation(
+ 'IconolabComment', content_type_field='content_type_id', object_id_field='object_pk')
+ validation_state = models.IntegerField(
+ choices=VALIDATION_STATES, default=UNVALIDATED)
validation_state_overriden = models.BooleanField(default=False)
-
+
objects = AnnotationManager()
-
+
def __str__(self):
- return str(self.annotation_guid)+":"+self.current_revision.title
-
+ return str(self.annotation_guid) + ":" + self.current_revision.title
+
@property
def awaiting_revisions_count(self):
return self.revisions.filter(state=AnnotationRevision.AWAITING).distinct().count()
-
+
@property
def accepted_revisions_count(self):
return self.revisions.filter(state=AnnotationRevision.ACCEPTED).distinct().count()
-
+
@property
def rejected_revisions_count(self):
return self.revisions.filter(state=AnnotationRevision.REJECTED).distinct().count()
-
+
@property
def studied_revisions_count(self):
return self.revisions.filter(state=AnnotationRevision.STUDIED).distinct().count()
-
+
@property
def total_revisions_count(self):
return self.revisions.distinct().count()
-
+
@property
def collection(self):
return self.image.collection
@property
def tag_labels(self):
- current_revision_tags = json.loads(self.current_revision.get_tags_json())
- return [tag_infos['tag_label'] for tag_infos in current_revision_tags if tag_infos.get('tag_label') is not None ]
-
+ current_revision_tags = json.loads(
+ self.current_revision.get_tags_json())
+ return [tag_infos['tag_label'] for tag_infos in current_revision_tags if tag_infos.get('tag_label') is not None]
+
def latest_revision_for_user(self, user):
user_revisions = self.revisions.filter(creator=user)
if user_revisions.exists():
return user_revisions.filter(creator=author).order_by("-created").first()
return None
-
+
@transaction.atomic
def make_new_revision(self, author, title, description, fragment, tags_json):
"""
@@ -386,7 +422,7 @@
# Revision will require validation
new_revision_state = AnnotationRevision.AWAITING
new_revision = AnnotationRevision(
- annotation = self,
+ annotation=self,
parent_revision=self.current_revision,
title=title,
description=description,
@@ -399,9 +435,10 @@
if new_revision.state == AnnotationRevision.ACCEPTED:
self.current_revision = new_revision
self.save()
- iconolab_signals.revision_created.send(sender=AnnotationRevision, instance=new_revision)
+ iconolab_signals.revision_created.send(
+ sender=AnnotationRevision, instance=new_revision)
return new_revision
-
+
@transaction.atomic
def validate_existing_revision(self, revision_to_validate):
"""
@@ -412,8 +449,9 @@
revision_to_validate.state = AnnotationRevision.ACCEPTED
revision_to_validate.save()
self.save()
- iconolab_signals.revision_accepted.send(sender=AnnotationRevision, instance=revision_to_validate)
-
+ iconolab_signals.revision_accepted.send(
+ sender=AnnotationRevision, instance=revision_to_validate)
+
@transaction.atomic
def reject_existing_revision(self, revision_to_reject):
"""
@@ -422,20 +460,23 @@
if revision_to_reject.state == AnnotationRevision.AWAITING:
revision_to_reject.state = AnnotationRevision.REJECTED
revision_to_reject.save()
- iconolab_signals.revision_rejected.send(sender=AnnotationRevision, instance=revision_to_reject)
-
+ iconolab_signals.revision_rejected.send(
+ sender=AnnotationRevision, instance=revision_to_reject)
+
@transaction.atomic
def merge_existing_revision(self, title, description, fragment, tags, revision_to_merge):
"""
Called when we're validating an awaiting revision whose parent isn't the current revision or if the awaiting revision was modified by the annotation author
"""
- merged_revision = self.make_new_revision(author=self.author, title=title, description=description, fragment=fragment, tags_json=tags)
+ merged_revision = self.make_new_revision(
+ author=self.author, title=title, description=description, fragment=fragment, tags_json=tags)
merged_revision.merge_parent_revision = revision_to_merge
merged_revision.save()
revision_to_merge.state = AnnotationRevision.STUDIED
revision_to_merge.save()
- iconolab_signals.revision_accepted.send(sender=AnnotationRevision, instance=revision_to_merge)
- self.current_revision=merged_revision
+ iconolab_signals.revision_accepted.send(
+ sender=AnnotationRevision, instance=revision_to_merge)
+ self.current_revision = merged_revision
self.save()
return merged_revision
@@ -444,100 +485,114 @@
"""
Stats objects for a given annotation, keep count of several values to be displayed in annotation pages
"""
- annotation = models.OneToOneField('Annotation', related_name='stats', blank=False, null=False)
- submitted_revisions_count = models.IntegerField(blank=True, null=True, default=1)
- awaiting_revisions_count = models.IntegerField(blank=True, null=True, default=0)
- accepted_revisions_count = models.IntegerField(blank=True, null=True, default=1)
+ annotation = models.OneToOneField(
+ 'Annotation', related_name='stats', blank=False, null=False)
+ submitted_revisions_count = models.IntegerField(
+ blank=True, null=True, default=1)
+ awaiting_revisions_count = models.IntegerField(
+ blank=True, null=True, default=0)
+ accepted_revisions_count = models.IntegerField(
+ blank=True, null=True, default=1)
contributors_count = models.IntegerField(blank=True, null=True, default=1)
views_count = models.IntegerField(blank=True, null=True, default=0)
comments_count = models.IntegerField(blank=True, null=True, default=0)
tag_count = models.IntegerField(blank=True, null=True, default=0)
- metacategories = models.ManyToManyField('MetaCategory', through='MetaCategoriesCountInfo', through_fields=('annotation_stats_obj', 'metacategory'))
-
+ metacategories = models.ManyToManyField(
+ 'MetaCategory', through='MetaCategoriesCountInfo', through_fields=('annotation_stats_obj', 'metacategory'))
+
def __str__(self):
- return "stats:for:"+str(self.annotation.annotation_guid)
-
+ return "stats:for:" + str(self.annotation.annotation_guid)
+
@property
def contributors(self):
- user_ids_list = self.annotation.revisions.filter(state__in=[AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]).values_list("author__id", flat=True)
+ user_ids_list = self.annotation.revisions.filter(state__in=[
+ AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]).values_list("author__id", flat=True)
return User.objects.filter(id__in=user_ids_list).distinct()
-
+
@property
def commenters(self):
- user_ids_list = IconolabComment.objects.filter(content_type__app_label="iconolab", content_type__model="annotation", object_pk=self.annotation.id).values_list("user__id", flat=True)
+ user_ids_list = IconolabComment.objects.filter(
+ content_type__app_label="iconolab", content_type__model="annotation", object_pk=self.annotation.id).values_list("user__id", flat=True)
return User.objects.filter(id__in=user_ids_list).distinct()
-
+
def set_tags_stats(self):
- self.tag_count = Tag.objects.filter(tagginginfo__revision = self.annotation.current_revision).distinct().count()
-
+ self.tag_count = Tag.objects.filter(
+ tagginginfo__revision=self.annotation.current_revision).distinct().count()
+
@property
def relevant_tags_count(self, score=settings.RELEVANT_TAGS_MIN_SCORE):
return TaggingInfo.objects.filter(revision=self.annotation.current_revision, relevancy__gte=score).distinct().count()
-
+
@property
def accurate_tags_count(self, score=settings.ACCURATE_TAGS_MIN_SCORE):
return TaggingInfo.objects.filter(revision=self.annotation.current_revision, accuracy__gte=score).distinct().count()
-
+
@transaction.atomic
def update_stats(self):
# views_count - Can't do much about views count
- # submitted_revisions_count
+ # submitted_revisions_count
annotation_revisions = self.annotation.revisions
self.submitted_revisions_count = annotation_revisions.count()
# aawaiting_revisions_count
- self.awaiting_revisions_count = annotation_revisions.filter(state=AnnotationRevision.AWAITING).count()
+ self.awaiting_revisions_count = annotation_revisions.filter(
+ state=AnnotationRevision.AWAITING).count()
# accepted_revisions_count
- self.accepted_revisions_count = annotation_revisions.filter(state=AnnotationRevision.ACCEPTED).count() + annotation_revisions.filter(state=AnnotationRevision.STUDIED).count()
+ self.accepted_revisions_count = annotation_revisions.filter(state=AnnotationRevision.ACCEPTED).count(
+ ) + annotation_revisions.filter(state=AnnotationRevision.STUDIED).count()
# comment_count
self.comments_count = XtdComment.objects.for_app_models("iconolab.annotation").filter(
- object_pk = self.annotation.pk,
+ object_pk=self.annotation.pk,
).count()
# contributors_count
self.contributors_count = len(self.contributors)
# tag_count
-
+
annotation_comments_with_metacategories = IconolabComment.objects.filter(
- content_type__app_label="iconolab",
- content_type__model="annotation",
- object_pk=self.annotation.id,
+ content_type__app_label="iconolab",
+ content_type__model="annotation",
+ object_pk=self.annotation.id,
metacategories__collection=self.annotation.image.item.collection
)
- m2m_objects = MetaCategoriesCountInfo.objects.filter(annotation_stats_obj=self)
+ m2m_objects = MetaCategoriesCountInfo.objects.filter(
+ annotation_stats_obj=self)
for obj in m2m_objects.all():
obj.count = 0
obj.save()
for comment in annotation_comments_with_metacategories.all():
for metacategory in comment.metacategories.all():
if metacategory not in self.metacategories.all():
- MetaCategoriesCountInfo.objects.create(annotation_stats_obj=self, metacategory=metacategory, count=1)
+ MetaCategoriesCountInfo.objects.create(
+ annotation_stats_obj=self, metacategory=metacategory, count=1)
else:
- m2m_object = MetaCategoriesCountInfo.objects.filter(annotation_stats_obj=self, metacategory=metacategory).first()
+ m2m_object = MetaCategoriesCountInfo.objects.filter(
+ annotation_stats_obj=self, metacategory=metacategory).first()
m2m_object.count += 1
m2m_object.save()
self.set_tags_stats()
self.save()
-
+
class MetaCategoriesCountInfo(models.Model):
"""
M2M class to keep a count of a given metacategory on a given annotation. metacategories are linked to comments, themselve linked to an annotation
"""
- annotation_stats_obj = models.ForeignKey('AnnotationStats', on_delete=models.CASCADE)
+ annotation_stats_obj = models.ForeignKey(
+ 'AnnotationStats', on_delete=models.CASCADE)
metacategory = models.ForeignKey('MetaCategory', on_delete=models.CASCADE)
count = models.IntegerField(default=1, blank=False, null=False)
-
+
def __str__(self):
- return "metacategory_count_for:"+self.metacategory.label+":on:"+str(self.annotation_stats_obj.annotation.annotation_guid)
+ return "metacategory_count_for:" + self.metacategory.label + ":on:" + str(self.annotation_stats_obj.annotation.annotation_guid)
class AnnotationRevision(models.Model):
"""
AnnotationRevisions objects are linked to an annotation and store the data of the annotation at a given time
-
+
A revision is always in one out of multiple states:
-
+
- Awaiting: the revision has been submitted but must be validated by the original author of the related annotation
- - Accepted: the revision has been accepted *as-is* by the author of the related annotation (this happens automatically
+ - Accepted: the revision has been accepted *as-is* by the author of the related annotation (this happens automatically
if the revision is created by the author of the annotation)
- Rejected: the revision has been rejected by the author of the related annotation
- Studied: the revision has been studied by the author of the related annotation and was either modified or at the very least compared with the current state
@@ -548,33 +603,37 @@
ACCEPTED = 1
REJECTED = 2
STUDIED = 3
-
+
REVISION_STATES = (
(AWAITING, 'awaiting'),
(ACCEPTED, 'accepted'),
(REJECTED, 'rejected'),
(STUDIED, 'studied'),
)
-
+
revision_guid = models.UUIDField(default=uuid.uuid4)
- annotation = models.ForeignKey('Annotation', related_name='revisions', null=False, blank=False)
- parent_revision = models.ForeignKey('AnnotationRevision', related_name='child_revisions', blank=True, null=True)
- merge_parent_revision = models.ForeignKey('AnnotationRevision', related_name='child_revisions_merge', blank=True, null=True)
+ annotation = models.ForeignKey(
+ 'Annotation', related_name='revisions', null=False, blank=False)
+ parent_revision = models.ForeignKey(
+ 'AnnotationRevision', related_name='child_revisions', blank=True, null=True)
+ merge_parent_revision = models.ForeignKey(
+ 'AnnotationRevision', related_name='child_revisions_merge', blank=True, null=True)
author = models.ForeignKey(User, null=True)
title = models.CharField(max_length=255)
description = models.TextField(null=True)
fragment = models.TextField()
- tags = models.ManyToManyField('Tag', through='TaggingInfo', through_fields=('revision', 'tag'))
+ tags = models.ManyToManyField(
+ 'Tag', through='TaggingInfo', through_fields=('revision', 'tag'))
state = models.IntegerField(choices=REVISION_STATES, default=AWAITING)
created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
- return str(self.revision_guid)+":"+self.title
+ return str(self.revision_guid) + ":" + self.title
def set_tags(self, tags_json_string):
"""
This method creates tags object and links them to the revision, from a given json that has the following format:
-
+
[
{
"tag_input": the tag string that has been provided. If it is an http(s?):// pattern, it means the tag is external, else it means it is a custom tag
@@ -594,47 +653,50 @@
tag_string = tag_data.get("tag_input")
tag_accuracy = tag_data.get("accuracy", 0)
tag_relevancy = tag_data.get("relevancy", 0)
-
- if tag_string.startswith("http://") or tag_string.startswith("https://"): #check if url
- if Tag.objects.filter(link=tag_string).exists(): #check if tag already exists
+
+ # check if url
+ if tag_string.startswith("http://") or tag_string.startswith("https://"):
+ # check if tag already exists
+ if Tag.objects.filter(link=tag_string).exists():
tag_obj = Tag.objects.get(link=tag_string)
else:
tag_obj = Tag.objects.create(
- link = tag_string,
+ link=tag_string,
)
else:
- new_tag_link = settings.BASE_URL+'/'+slugify(tag_string)
+ new_tag_link = settings.BASE_URL + '/' + slugify(tag_string)
if Tag.objects.filter(link=new_tag_link).exists():
# Somehow we received a label for an existing tag
tag_obj = Tag.objects.get(link=new_tag_link)
else:
tag_obj = Tag.objects.create(
- label = tag_string,
- label_slug = slugify(tag_string),
- description = "",
- link = settings.INTERNAL_TAGS_URL+'/'+slugify(tag_string),
- collection = self.annotation.image.item.collection
+ label=tag_string,
+ label_slug=slugify(tag_string),
+ description="",
+ link=settings.INTERNAL_TAGS_URL +
+ '/' + slugify(tag_string),
+ collection=self.annotation.image.item.collection
)
tag_info = TaggingInfo.objects.create(
- tag=tag_obj,
+ tag=tag_obj,
revision=self,
- accuracy = tag_accuracy,
- relevancy = tag_relevancy
+ accuracy=tag_accuracy,
+ relevancy=tag_relevancy
)
-
+
def get_tags_json(self):
"""
This method returns the json data that will be sent to the js to display tags for the revision.
-
+
The json data returned will be of the following format:
-
+
[
{
"tag_label": the tag label for display purposes,
"tag_link": the link of the tag, for instance for dbpedia links,
"accuracy": the accuracy value of the tag,
"relevancy": the relevancy value of the tag,
- "is_internal": will be True if the tag is 'internal', meaning specific to Iconolab and
+ "is_internal": will be True if the tag is 'internal', meaning specific to Iconolab and
not an external tag like a dbpedia reference for instance
},
{
@@ -643,15 +705,16 @@
]
"""
def fetch_from_dbpedia(uri, lang, source):
- sparql_template = 'select distinct * where { <<%uri%>> rdfs:label ?l FILTER( langMatches( lang(?l), "<%lang%>" ) ) }'
- sparql_query = re.sub("<%uri%>", uri, re.sub("<%lang%>", lang, sparql_template))
- sparql_query_url = source+'sparql'
+ sparql_template = 'select distinct * where { <<%uri%>> rdfs:label ?l FILTER( langMatches( lang(?l), "<%lang%>" ) ) }'
+ sparql_query = re.sub("<%uri%>", uri, re.sub(
+ "<%lang%>", lang, sparql_template))
+ sparql_query_url = source + 'sparql'
try:
dbpedia_resp = requests.get(
- sparql_query_url,
+ sparql_query_url,
params={
- "query": sparql_query,
- "format": "json"
+ "query": sparql_query,
+ "format": "json"
}
)
except:
@@ -667,7 +730,7 @@
if variable_bindings:
label_data = variable_bindings.pop()
return label_data.get("l", {"value": False}).get("value")
-
+
final_list = []
for tagging_info in self.tagginginfo_set.select_related("tag").all():
if tagging_info.tag.is_internal():
@@ -680,16 +743,17 @@
})
else:
tag_link = tagging_info.tag.link
- #import label from external
+ # import label from external
externaL_repos_fetch_dict = {
"http://dbpedia.org/": fetch_from_dbpedia,
"http://fr.dbpedia.org/": fetch_from_dbpedia
}
try:
- (source, fetch_label) = next(item for item in externaL_repos_fetch_dict.items() if tag_link.startswith(item[0]))
+ (source, fetch_label) = next(
+ item for item in externaL_repos_fetch_dict.items() if tag_link.startswith(item[0]))
tag_label = fetch_label(tag_link, "fr", source)
- if not tag_label: # Error happened and we got False as a fetch return
- tag_label = tagging_info.tag.label
+ if not tag_label: # Error happened and we got False as a fetch return
+ tag_label = tagging_info.tag.label
else:
tagging_info.tag.label = tag_label
tagging_info.tag.save()
@@ -702,13 +766,13 @@
})
except StopIteration:
pass
- return json.dumps(final_list)
+ return json.dumps(final_list)
class Tag(models.Model):
"""
Tag objects that are linked to revisions.
-
+
Each tag is linked to a specific collection, this is important for internal tags
so each collection can build its own vocabulary
"""
@@ -717,77 +781,80 @@
link = models.URLField(unique=True)
description = models.CharField(max_length=255, blank=True, null=True)
collection = models.ForeignKey('Collection', blank=True, null=True)
-
+
def is_internal(self):
return self.link.startswith(settings.INTERNAL_TAGS_URL)
def __str__(self):
- return self.label_slug+":"+self.label
+ return self.label_slug + ":" + self.label
class TaggingInfo(models.Model):
"""
M2M object for managing tag relation to a revision with its associated relevancy and accuracy
"""
- revision = models.ForeignKey('AnnotationRevision', on_delete=models.CASCADE)
+ revision = models.ForeignKey(
+ 'AnnotationRevision', on_delete=models.CASCADE)
tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
accuracy = models.IntegerField()
relevancy = models.IntegerField()
-
+
def __str__(self):
- return str(str(self.tag.label_slug)+":to:"+str(self.revision.revision_guid))
+ return str(str(self.tag.label_slug) + ":to:" + str(self.revision.revision_guid))
-
+
class IconolabComment(XtdComment):
"""
Comment objects that extends XtdComment model, itself extending the django-contrib-comments model.
-
+
Each comment can have 0 or 1 revision, if it is a comment created alongside a revision
Each comment can have a set of metacategories
"""
- revision = models.ForeignKey('AnnotationRevision', related_name='creation_comment', null=True, blank=True)
- metacategories = models.ManyToManyField('MetaCategory', through='MetaCategoryInfo', through_fields=('comment', 'metacategory'))
-
+ revision = models.ForeignKey(
+ 'AnnotationRevision', related_name='creation_comment', null=True, blank=True)
+ metacategories = models.ManyToManyField(
+ 'MetaCategory', through='MetaCategoryInfo', through_fields=('comment', 'metacategory'))
+
objects = XtdComment.objects
-
+
def __str__(self):
return str(self.id)
-
+
class Meta:
ordering = ["thread_id", "id"]
-
+
@property
def annotation(self):
if self.content_type.app_label == "iconolab" and self.content_type.model == "annotation":
return Annotation.objects.get(pk=self.object_pk)
return None
-
+
def get_comment_page(self):
"""
Shortcut function to get page for considered comment, with COMMENTS_PER_PAGE_DEFAULT comments per page, used for notifications links generation
"""
return (IconolabComment.objects.for_app_models("iconolab.annotation").filter(
object_pk=self.object_pk,
- ).filter(thread_id__gte=self.thread_id).filter(order__lte=self.order).count() +1) // settings.COMMENTS_PER_PAGE_DEFAULT + 1
+ ).filter(thread_id__gte=self.thread_id).filter(order__lte=self.order).count() + 1) // settings.COMMENTS_PER_PAGE_DEFAULT + 1
-
+
class MetaCategory(models.Model):
"""
Metacategories are objects that can be linked to a comment to augment it with meaning (depending on the metacategories defined for a given collection)
-
+
Metacategories can trigger notifications when they are linked to a given coment depending on their trigger_notifications property:
-
+
- NONE : Notifies nobody
- CONTRIBUTORS : Notifies contributors (revision owners) on target annotation
- COMMENTERS : Notifies commenters (contributors + comment owners) on target annotation
- COLLECTION_ADMINS : Notifies collection admins
-
+
Metacategories can be used to consider an annotation as "validated" if a certain agreement threshold is reached using their validation_value property
-
+
- NEUTRAL : The metacategory doesn't affect the validation state
- AGREEMENT : The metacategory can be used to validate the annotation when linked to a comment on said annotation
- DISAGREEMENT : The metacategory can be used to unvalidate the annotation when linked to a comment on said annotation
-
+
"""
NONE = 0
CONTRIBUTORS = 1
@@ -799,7 +866,7 @@
(COMMENTERS, 'commenters'),
(COLLECTION_ADMINS, 'collection admins'),
)
-
+
NEUTRAL = 0
AGREEMENT = 1
DISAGREEMENT = 2
@@ -808,15 +875,17 @@
(AGREEMENT, 'agreement'),
(DISAGREEMENT, 'disagreement'),
)
-
+
collection = models.ForeignKey(Collection, related_name="metacategories")
label = models.CharField(max_length=255)
- triggers_notifications = models.IntegerField(choices=NOTIFIED_USERS, default=NONE)
- validation_value = models.IntegerField(choices=VALIDATION_VALUES, default=NEUTRAL)
-
+ triggers_notifications = models.IntegerField(
+ choices=NOTIFIED_USERS, default=NONE)
+ validation_value = models.IntegerField(
+ choices=VALIDATION_VALUES, default=NEUTRAL)
+
def __str__(self):
- return self.label+":"+self.collection.name
-
+ return self.label + ":" + self.collection.name
+
class MetaCategoryInfo(models.Model):
"""
@@ -824,9 +893,9 @@
"""
comment = models.ForeignKey('IconolabComment', on_delete=models.CASCADE)
metacategory = models.ForeignKey('MetaCategory', on_delete=models.CASCADE)
-
+
def __str__(self):
- return "metacategory:"+self.metacategory.label+":on:"+self.comment.id
+ return "metacategory:" + self.metacategory.label + ":on:" + self.comment.id
class CommentAttachement(models.Model):
@@ -842,20 +911,23 @@
(IMAGE, 'image'),
(PDF, 'pdf')
)
-
- comment = models.ForeignKey('IconolabComment', related_name='attachments', on_delete=models.CASCADE)
+
+ comment = models.ForeignKey(
+ 'IconolabComment', related_name='attachments', on_delete=models.CASCADE)
attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=0)
data = models.TextField(blank=False)
-
+
class UserProfile(models.Model):
"""
UserProfile objects are extensions of user model
-
+
As of v0.0.19 they are used to define collection admins. Each user can thus managed 0-N collections.
"""
- user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
- managed_collections = models.ManyToManyField('Collection', related_name='admins', blank=True)
-
+ user = models.OneToOneField(
+ User, related_name='profile', on_delete=models.CASCADE)
+ managed_collections = models.ManyToManyField(
+ 'Collection', related_name='admins', blank=True)
+
def __str__(self):
- return "profile:"+self.user.username
\ No newline at end of file
+ return "profile:" + self.user.username
--- a/src/iconolab/views/objects.py Fri Dec 16 13:24:55 2016 +0100
+++ b/src/iconolab/views/objects.py Wed Jan 18 14:11:30 2017 +0100
@@ -25,11 +25,12 @@
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
+ 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()
@@ -50,8 +51,10 @@
"""
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)
+ 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 = ()
@@ -81,11 +84,11 @@
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)
@@ -98,7 +101,7 @@
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 = {}
@@ -122,11 +125,11 @@
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
-
+ return pagination_data
+
class CollectionHomepageView(View, ContextMixin, IconolabObjectView):
"""
- View that displays a collection and four panels to show relevant paginated lists for collection:
+ 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
@@ -135,10 +138,10 @@
def get(self, request, *args, **kwargs):
"""
Template is iconolab/collection_home.html
-
- Url args are:
- - collection_name: 'name' attribute of the requested collection
-
+
+ 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
@@ -149,7 +152,7 @@
- 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
@@ -158,7 +161,7 @@
- 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
@@ -167,7 +170,7 @@
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'))
@@ -177,7 +180,7 @@
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:
@@ -186,7 +189,7 @@
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:
@@ -195,7 +198,7 @@
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:
@@ -204,22 +207,22 @@
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").all()
context["items_pagination_data"] = self.get_pagination_data(
- items_list,
- items_page,
- items_per_page,
+ items_list,
+ items_page,
+ items_per_page,
adjacent_pages_count,
perpage_range=[6, 12, 48, 192],
trailing_qarg="&recent_page="+str(recent_page)
@@ -229,16 +232,16 @@
+"&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,
+ recent_annotations,
+ recent_page,
+ recent_per_page,
adjacent_pages_count,
trailing_qarg="&items_page="+str(items_page)
+"&items_perpage="+str(items_per_page)
@@ -247,16 +250,16 @@
+"&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,
+ revised_annotations,
+ revised_page,
+ revised_per_page,
adjacent_pages_count,
trailing_qarg="&items_page="+str(items_page)
+"&items_perpage="+str(items_per_page)
@@ -265,7 +268,7 @@
+"&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,
@@ -275,9 +278,9 @@
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,
+ contributions_annotations,
+ contributions_page,
+ contributions_per_page,
adjacent_pages_count,
trailing_qarg="&items_page="+str(items_page)
+"&items_perpage="+str(items_per_page)
@@ -286,7 +289,7 @@
+"&revised_page="+str(revised_page)
+"&revised_perpage="+str(revised_per_page)
)
-
+
return render(request, 'iconolab/collection_home.html', context)
@@ -298,16 +301,16 @@
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
-
+ - 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
@@ -320,13 +323,13 @@
'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)]:
@@ -340,7 +343,7 @@
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
@@ -359,7 +362,7 @@
except PageNotAnInteger:
annotations = annotations_paginator.page(1)
except EmptyPage:
- annotations = annotations_paginator.page(recent_paginator.num_pages)
+ annotations = annotations_paginator.page(recent_paginator.num_pages)
context['images'].append({
'obj' : image,
'annotations': annotations
@@ -444,10 +447,11 @@
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.
+ 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', '')
@@ -458,23 +462,23 @@
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
-
+
+ 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
+ - 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)
@@ -535,7 +539,7 @@
def get(self, request, *args, **kwargs):
"""
- Exactly the same as ShowAnnotationView but without all the data around comments
+ Exactly the same as ShowAnnotationView but without all the data around comments
"""
success, result = self.check_kwargs(kwargs)
if success:
@@ -556,7 +560,7 @@
class EditAnnotationView(View, ContextMixin, IconolabObjectView):
"""
- View that handles displaying the edition form and editing an annotation
+ 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)
@@ -631,13 +635,13 @@
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
+
+ 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