# HG changeset patch # User durandn # Date 1481884510 -3600 # Node ID f52b0f6e2cd99af849a303b792e431e4112e46ed # Parent d8fcfac848ed79eba2c55c952648413321f3ba45 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. diff -r d8fcfac848ed -r f52b0f6e2cd9 src/iconolab/migrations/0018_auto_20161215_1731.py --- /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), + ), + ] diff -r d8fcfac848ed -r f52b0f6e2cd9 src/iconolab/models.py --- 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 diff -r d8fcfac848ed -r f52b0f6e2cd9 src/iconolab/signals/handlers.py --- 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)