added fields on metacategories, annotations to track the validation state of the annotation, with example handler in signals that check and update the state when a relevant metacategory is posted.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0018_auto_20161215_1731.py Fri Dec 16 11:35:10 2016 +0100
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-12-15 17:31
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('iconolab', '0017_collection_show_image_on_home'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='annotation',
+ name='validation_state',
+ field=models.IntegerField(choices=[(0, 'unvalidated'), (1, 'validated')], default=0),
+ ),
+ migrations.AddField(
+ model_name='annotation',
+ name='validation_state_overriden',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='metacategory',
+ name='validation_value',
+ field=models.IntegerField(choices=[(0, 'neutral'), (1, 'agreement'), (2, 'disagreement')], default=0),
+ ),
+ ]
--- a/src/iconolab/models.py Thu Dec 15 16:35:01 2016 +0100
+++ b/src/iconolab/models.py Fri Dec 16 11:35:10 2016 +0100
@@ -313,7 +313,17 @@
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)
"""
+ UNVALIDATED = 0
+ VALIDATED = 1
+ VALIDATION_STATES = (
+ (UNVALIDATED, 'unvalidated'),
+ (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)
@@ -321,6 +331,8 @@
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)
+ validation_state_overriden = models.BooleanField(default=False)
objects = AnnotationManager()
@@ -769,12 +781,18 @@
- 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
COMMENTERS = 2
COLLECTION_ADMINS = 3
-
NOTIFIED_USERS = (
(NONE, 'none'),
(CONTRIBUTORS, 'contributors'),
@@ -782,9 +800,19 @@
(COLLECTION_ADMINS, 'collection admins'),
)
+ NEUTRAL = 0
+ AGREEMENT = 1
+ DISAGREEMENT = 2
+ VALIDATION_VALUES = (
+ (NEUTRAL, 'neutral'),
+ (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)
def __str__(self):
return self.label
--- a/src/iconolab/signals/handlers.py Thu Dec 15 16:35:01 2016 +0100
+++ b/src/iconolab/signals/handlers.py Fri Dec 16 11:35:10 2016 +0100
@@ -52,7 +52,7 @@
def increment_stats_on_new_metacategory(sender, instance, created, **kwargs):
"""
- Signal to increment stats on annotation when a metacategory is linked to a comment
+ Signal to increment stats on annotation when a metacategory is linked to a comment (on comment creation)
"""
from iconolab.models import MetaCategoryInfo, MetaCategoriesCountInfo
if created and sender == MetaCategoryInfo:
@@ -65,7 +65,6 @@
m2m_object = MetaCategoriesCountInfo.objects.get(annotation_stats_obj=annotation.stats, metacategory=metacategory)
m2m_object.count += 1
m2m_object.save()
- logger.debug("NEW METACATEGORY %r on comment %r on annotation %r", metacategory, comment, annotation)
def increment_stats_on_accepted_revision(sender, instance, **kwargs):
"""
@@ -101,7 +100,41 @@
image.stats.save()
+def update_annotation_validation_state(sender, instance, created, **kwargs):
+ """
+ Example signal to check if a metacategory added on an annotation is enough to make the annotation "validated"
+
+ We listen to saves on the m2m for the metacategory count in annotation stats,
+ check if the corresponding metacategory can affect the validation state,
+ check if the state wasn't overriden, and compute the state again if checks were passed
+ """
+ from iconolab.models import MetaCategory, MetaCategoryInfo, MetaCategoriesCountInfo
+ if sender == MetaCategoriesCountInfo:
+ metacategory = instance.metacategory
+ annotation = instance.annotation_stats_obj.annotation
+ if metacategory.validation_value != MetaCategory.NEUTRAL and not annotation.validation_state_overriden:
+ non_neutral_mtcgs_count = 0
+ validating_mtcgs_count = 0
+ unvalidating_mtcgs_count = 0
+ # For each metacategory linked to annotation stats we check if it affects the validation state and keep track of counts if this is the case
+ for metacategory_infos in annotation.stats.metacategoriescountinfo_set.all():
+ if metacategory_infos.metacategory.validation_value == MetaCategory.AGREEMENT:
+ validating_mtcgs_count += metacategory_infos.count
+ non_neutral_mtcgs_count += metacategory_infos.count
+ if metacategory_infos.metacategory.validation_value == MetaCategory.DISAGREEMENT:
+ unvalidating_mtcgs_count += metacategory_infos.count
+ non_neutral_mtcgs_count += metacategory_infos.count
+ if non_neutral_mtcgs_count > 3 and (validating_mtcgs_count / non_neutral_mtcgs_count > 0.6):
+ annotation.validation_state = True
+ annotation.save()
+ if non_neutral_mtcgs_count > 3 and (validating_mtcgs_count / non_neutral_mtcgs_count < 0.4):
+ annotation.validation_state = False
+ annotation.save()
+
def notify_users_on_new_comment(sender, instance, **kwargs):
+ """
+ Signal to notify users when a comment is created. Notified users are: annotation author, parent comment author
+ """
from iconolab.models import IconolabComment, Annotation, MetaCategory, MetaCategoryInfo
if sender == IconolabComment and instance.content_type.app_label == 'iconolab' and instance.content_type.model == 'annotation':
comment_annotation = Annotation.objects.get(id=instance.object_pk)
@@ -118,6 +151,9 @@
notify.send(instance.user, recipient=comment_annotation.author, verb='a écrit un commentaire sur votre annotation', action_object=instance, target=comment_annotation)
def notify_users_on_metacategory(sender, instance, created, **kwargs):
+ """
+ Signal to notify users when a comment is created. Notified users are: annotation author, parent comment author
+ """
from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation, UserProfile
if sender == MetaCategoryInfo and created:
related_metacategory = instance.metacategory
@@ -159,6 +195,7 @@
post_save.connect(increment_annotations_count)
post_save.connect(increment_stats_on_new_comment)
post_save.connect(increment_stats_on_new_metacategory)
+post_save.connect(update_annotation_validation_state)
revision_created.connect(increment_stats_on_new_revision)
revision_accepted.connect(increment_stats_on_accepted_revision)
revision_rejected.connect(increment_stats_on_rejected_revision)