# HG changeset patch # User durandn # Date 1471510735 -7200 # Node ID 454e39dced1f0d27dbc5f32296eca4cc4068d54e # Parent 81d82b1f431a367f45c1d56d045f926f34c03084# Parent e13fed7f0837208731b2f9b1472fe2284966120d Merge with e13fed7f0837208731b2f9b1472fe2284966120d diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/__init__.py --- a/src/iconolab/__init__.py Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/__init__.py Thu Aug 18 10:58:55 2016 +0200 @@ -0,0 +1,46 @@ +VERSION = (0, 0, 1, "alpha", 0) + +VERSION_STR = ".".join(map(lambda i:"%02d" % (i,), VERSION[:2])) + +### +# https://github.com/django/django/blob/1.9.1/django/utils/version.py +# +def get_version(version): + "Returns a PEP 440-compliant version number from VERSION." + version = get_complete_version(version) + + # Now build the two parts of the version number: + # main = X.Y[.Z] + # sub = .devN - for pre-alpha releases + # | {a|b|rc}N - for alpha, beta, and rc releases + + main = get_main_version(version) + + sub = '' + if version[3] == 'alpha' and version[4] == 0: + sub = '.dev' + + elif version[3] != 'final': + mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'} + sub = mapping[version[3]] + str(version[4]) + + return str(main + sub) + +def get_complete_version(version): + """ + then checks for correctness of the tuple provided. + """ + assert len(version) == 5 + assert version[3] in ('alpha', 'beta', 'rc', 'final') + + return version + +def get_main_version(version=None): + "Returns main version (X.Y[.Z]) from VERSION." + version = get_complete_version(version) + parts = 2 if version[2] == 0 else 3 + return '.'.join(str(x) for x in version[:parts]) + +__version__ = get_version(VERSION) + +default_app_config = 'iconolab.apps.IconolabApp' diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/admin.py --- a/src/iconolab/admin.py Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/admin.py Thu Aug 18 10:58:55 2016 +0200 @@ -1,1 +1,20 @@ -from django.contrib import admin \ No newline at end of file +from django.contrib import admin +from iconolab import models + +# Iconolab objects +admin.site.register(models.Collection) +admin.site.register(models.Item) +admin.site.register(models.Image) +admin.site.register(models.Annotation) +admin.site.register(models.AnnotationRevision) +# Tags +admin.site.register(models.Tag) +admin.site.register(models.TaggingInfo) + +# User +admin.site.register(models.UserProfile) + +# Comment system +admin.site.register(models.IconolabComment) +admin.site.register(models.MetaCategory) +admin.site.register(models.MetaCategoryInfo) \ No newline at end of file diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/migrations/0010_auto_20160816_1242.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/iconolab/migrations/0010_auto_20160816_1242.py Thu Aug 18 10:58:55 2016 +0200 @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-16 12:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('iconolab', '0009_auto_20160812_1417'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='description', + field=models.TextField(null=True), + ), + ] diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/migrations/0011_userprofile.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/iconolab/migrations/0011_userprofile.py Thu Aug 18 10:58:55 2016 +0200 @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-16 14:46 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('iconolab', '0010_auto_20160816_1242'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('administers_collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='collection', to='iconolab.Collection')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/migrations/0012_auto_20160817_1019.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/iconolab/migrations/0012_auto_20160817_1019.py Thu Aug 18 10:58:55 2016 +0200 @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-17 10:19 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('iconolab', '0011_userprofile'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL), + ), + ] diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/models.py --- a/src/iconolab/models.py Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/models.py Thu Aug 18 10:58:55 2016 +0200 @@ -19,18 +19,23 @@ def is_internal(self): return self.link.startswith(settings.INTERNAL_TAGS_URL) + def __str__(self): + return self.label_slug+":"+self.label class TaggingInfo(models.Model): 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 self.tag.label_slug+":to:"+self.revision.revision_guid class Collection(models.Model): name = models.SlugField(max_length=50, unique=True) verbose_name = models.CharField(max_length=50, null=True, blank=True) - description = models.CharField(max_length=255) + description = models.TextField(null=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) @@ -42,7 +47,9 @@ class Item(models.Model): 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 class ItemMetadata(models.Model): item = models.OneToOneField('Item', related_name='metadatas') @@ -61,7 +68,10 @@ @property def get_joconde_url(self): - return self.joconde_ref + return settings.JOCONDE_NOTICE_BASE_URL+self.joconde_ref.rjust(11, '0') + + def __str__(self): + return "metadatas:for:"+str(self.item.item_guid) class ImageStats(models.Model): @@ -73,6 +83,9 @@ 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:"+self.image.image_guid + def set_tags_stats(self): self.tag_count = Tag.objects.filter(tagginginfo__revision__annotation__image = self.image).distinct().count() @@ -106,7 +119,10 @@ 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 + @property def collection(self): return self.item.collection.name @@ -139,9 +155,6 @@ def measurements(self): return self.item.metadatas.measurements - def __str__(self): - return self.name - @property def tags(self): tag_list = [] @@ -183,6 +196,7 @@ 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) return new_annotation @@ -196,6 +210,9 @@ comments_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.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) @@ -241,6 +258,9 @@ objects = AnnotationManager() + def __str__(self): + return str(self.annotation_guid)+":"+self.current_revision.title + @property def awaiting_revisions_count(self): return self.revisions.filter(state=AnnotationRevision.AWAITING).distinct().count() @@ -338,6 +358,9 @@ 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 + def set_tags(self, tags_json_string): try: tags_dict = json.loads(tags_json_string) @@ -446,6 +469,9 @@ objects = XtdComment.objects + def __str__(self): + return self.id + class Meta: ordering = ["thread_id", "id"] @@ -480,7 +506,9 @@ class MetaCategoryInfo(models.Model): 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 class CommentAttachement(models.Model): @@ -495,4 +523,11 @@ 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) \ No newline at end of file + data = models.TextField(blank=False) + +class UserProfile(models.Model): + user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE) + administers_collection = models.ForeignKey('Collection', related_name='collection', blank=True, null=True) + + def __str__(self): + return "profile:"+self.user.username \ No newline at end of file diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/settings/__init__.py --- a/src/iconolab/settings/__init__.py Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/settings/__init__.py Thu Aug 18 10:58:55 2016 +0200 @@ -10,7 +10,7 @@ https://docs.djangoproject.com/en/1.9/ref/settings/ """ -import os +import os, logging # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -46,7 +46,6 @@ # Application definition INSTALLED_APPS = [ - 'restapi', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -82,6 +81,58 @@ ] +# Logging + +LOG_FILE = os.path.abspath(os.path.join(BASE_DIR,"../../run/log/log.txt")) +LOG_LEVEL = logging.DEBUG +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters' : { + 'simple' : { + 'format': "%(asctime)s - %(levelname)s : %(message)s", + }, + 'semi-verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s %(message)s' + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'stream_to_console': { + 'level': LOG_LEVEL, + 'class': 'logging.StreamHandler' + }, + 'file': { + 'level': LOG_LEVEL, + 'class': 'logging.FileHandler', + 'filename': LOG_FILE, + 'formatter': 'semi-verbose', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['file'], + 'level': LOG_LEVEL, + 'propagate': True, + }, + 'iconolab': { + 'handlers': ['file'], + 'level': LOG_LEVEL, + 'propagate': True, + }, + } +} + + ROOT_URLCONF = 'iconolab.urls' TEMPLATES = [ diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/settings/dev.py.tmpl --- a/src/iconolab/settings/dev.py.tmpl Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/settings/dev.py.tmpl Thu Aug 18 10:58:55 2016 +0200 @@ -42,7 +42,6 @@ # Application definition INSTALLED_APPS = [ - 'restapi', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -52,14 +51,14 @@ 'django.contrib.sites', 'django_comments', 'django_comments_xtd', - 'notifications', - 'reversion', - 'reversion_compare', + 'haystack', 'iconolab.apps.IconolabApp', 'sorl.thumbnail', 'notifications' ] + + COMMENTS_APP = "django_comments_xtd" COMMENTS_XTD_MODEL = "iconolab.models.IconolabComment" COMMENTS_XTD_FORM_CLASS = 'iconolab.forms.comments.IconolabCommentForm' @@ -118,6 +117,58 @@ } } +# Logging + +LOG_FILE = os.path.abspath(os.path.join(BASE_DIR,"../../run/log/log.txt")) +LOG_LEVEL = logging.DEBUG +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters' : { + 'simple' : { + 'format': "%(asctime)s - %(levelname)s : %(message)s", + }, + 'semi-verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s %(message)s' + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'stream_to_console': { + 'level': LOG_LEVEL, + 'class': 'logging.StreamHandler' + }, + 'file': { + 'level': LOG_LEVEL, + 'class': 'logging.FileHandler', + 'filename': LOG_FILE, + 'formatter': 'semi-verbose', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['file'], + 'level': LOG_LEVEL, + 'propagate': True, + }, + 'iconolab': { + 'handlers': ['file'], + 'level': LOG_LEVEL, + 'propagate': True, + }, + } +} + + # Haystack connection HAYSTACK_CONNECTIONS = { 'default': { @@ -171,4 +222,26 @@ USE_TZ = True + +IMPORT_FIELDS_DICT = { + "AUTR": [], + "ECOLE": [], + "TITR": ["Titre"], + "DENO": [], + "APPL": [], + "PERI": ["Période"], + "MILL": [], + "EPOCH": [], + "TECH": [], + "DIMS": ["Dimensions"], + "EPOCH": [], + "LIEUX": [], + "DECV": [], + "LOCA": ["Localisation"], + "PHOT": ["Photo"], + "INV": ["No inventaire",], + "REF": ["REFERENCE"], +} + INTERNAL_TAGS_URL = BASE_URL +JOCONDE_NOTICE_BASE_URL = "http://www.culture.gouv.fr/public/mistral/joconde_fr?ACTION=CHERCHER&FIELD_98=REF&VALUE_98=" diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/signals/handlers.py --- a/src/iconolab/signals/handlers.py Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/signals/handlers.py Thu Aug 18 10:58:55 2016 +0200 @@ -12,22 +12,23 @@ def increment_stats_on_new_revision(sender, instance, **kwargs): from iconolab.models import AnnotationRevision if sender == AnnotationRevision: - # Annotation stats - annotation = instance.annotation - annotation.stats.submitted_revisions_count += 1 - if instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]: - annotation.stats.accepted_revisions_count += 1 - if instance.state == AnnotationRevision.ACCEPTED and instance.merge_parent_revision is not None and instance.merge_parent_revision.state == AnnotationRevision.STUDIED: - annotation.stats.awaiting_revisions_count -= 1 - if instance.state in [AnnotationRevision.AWAITING]: - annotation.stats.awaiting_revisions_count += 1 - annotation.stats.set_tags_stats() - annotation.stats.save() - # Image stats - image = instance.annotation.image - image.stats.submitted_revisions_count += 1 - image.stats.set_tags_stats() - image.stats.save() + if instance.parent_revision: + # Annotation stats + annotation = instance.annotation + annotation.stats.submitted_revisions_count += 1 + if instance.state in [AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]: + annotation.stats.accepted_revisions_count += 1 + if instance.state == AnnotationRevision.ACCEPTED and instance.merge_parent_revision is not None and instance.merge_parent_revision.state == AnnotationRevision.STUDIED: + annotation.stats.awaiting_revisions_count -= 1 + if instance.state in [AnnotationRevision.AWAITING]: + annotation.stats.awaiting_revisions_count += 1 + annotation.stats.set_tags_stats() + annotation.stats.save() + # Image stats + image = instance.annotation.image + image.stats.submitted_revisions_count += 1 + image.stats.set_tags_stats() + image.stats.save() def increment_stats_on_new_comment(sender, instance, created, **kwargs): @@ -85,12 +86,12 @@ def notify_users_on_metacategory(sender, instance, created, **kwargs): - from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation + from iconolab.models import MetaCategory, MetaCategoryInfo, Annotation, UserProfile if sender == MetaCategoryInfo and created: related_metacategory = instance.metacategory related_comment = instance.comment if related_comment.content_type.app_label == "iconolab" and related_comment.content_type.model == "annotation": - comment_annotation = Annotation.objects.get(id=related_comment.object_pk) + comment_annotation = Annotation.objects.prefetch_related("image__item__collection").get(id=related_comment.object_pk) if related_metacategory.triggers_notifications == MetaCategory.COMMENTERS: for commenter in comment_annotation.stats.commenters.exclude(id=related_comment.user.id).all(): notify.send(related_comment.user, recipient=commenter, verb='a fait un appel à contribution', action_object=related_comment, target=comment_annotation) @@ -98,7 +99,8 @@ for contributor in comment_annotation.stats.contributors.exclude(id=related_comment.user.id).all(): notify.send(related_comment.user, recipient=contributor, verb='a fait un appel à contribution', action_object=related_comment, target=comment_annotation) if related_metacategory.triggers_notifications == MetaCategory.COLLECTION_ADMINS: - pass + for collection_admin in UserProfile.objects.filter(administers_collection=comment_annotation.image.item.collection).all(): + notify.send(related_comment.user, recipient=collection_admin.user, verb='a fait un appel à expertise', action_object=related_comment, target=comment_annotation) def notify_users_on_new_revision(sender, instance, **kwargs): from iconolab.models import AnnotationRevision @@ -114,6 +116,15 @@ notify.send(instance.annotation.author, recipient=instance.author, verb='a étudié votre révision', action_object=instance, target=instance.annotation) +def create_user_profile(sender, instance, created, **kwargs): + from iconolab.models import UserProfile + from django.contrib.auth.models import User + if sender == User and created: + UserProfile.objects.create(user=instance) + +# User profile connect +post_save.connect(create_user_profile) + # Stats handlers connect post_save.connect(increment_annotations_count) post_save.connect(increment_stats_on_new_comment) diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/static/iconolab/css/iconolab.css --- a/src/iconolab/static/iconolab/css/iconolab.css Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/static/iconolab/css/iconolab.css Thu Aug 18 10:58:55 2016 +0200 @@ -70,6 +70,8 @@ cursor: pointer; } +/* BADGES */ + .badge-error { background-color: #b94a48; } @@ -89,6 +91,30 @@ .notif-badge{ margin-bottom: 5px; } + +/* USER PAGE */ + .show-all-notifications{ cursor: pointer; +} +.annotation-panel{ + min-width: 535px; +} +.annotation-detail{ + vertical-align: middle; + display:inline-block; + margin-right: 15px; + margin-left: 15px; +} +.no-user-annotation{ + margin-left: 15px; +} +.dt-annotation{ + margin-bottom: 10px; +} +.userpage-annotation-btn{ + display:inline-block; + vertical-align: top; + margin-top: 15px; + margin-right: 15px; } \ No newline at end of file diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/static/iconolab/img/glossary_example.png Binary file src/iconolab/static/iconolab/img/glossary_example.png has changed diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/static/iconolab/img/logo_IRI_footer.png Binary file src/iconolab/static/iconolab/img/logo_IRI_footer.png has changed diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/static/iconolab/img/logo_mcc_footer.png Binary file src/iconolab/static/iconolab/img/logo_mcc_footer.png has changed diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/templates/iconolab/detail_annotation.html --- a/src/iconolab/templates/iconolab/detail_annotation.html Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/templates/iconolab/detail_annotation.html Thu Aug 18 10:58:55 2016 +0200 @@ -27,7 +27,7 @@ {% endthumbnail %}
- Revoir l'objet + Voir l'objet de cette annotation Voir les annotations sur l'image @@ -219,7 +219,7 @@
- +
{% endif %} diff -r e13fed7f0837 -r 454e39dced1f src/iconolab/templates/iconolab/detail_image.html --- a/src/iconolab/templates/iconolab/detail_image.html Thu Aug 18 10:49:18 2016 +0200 +++ b/src/iconolab/templates/iconolab/detail_image.html Thu Aug 18 10:58:55 2016 +0200 @@ -10,7 +10,7 @@Iconolab est une plateforme contributive grâce à laquelle les utilisateurs peuvent explorer et annoter des collections (fond) d'images fournis par plusieurs musées et référencés dans la base de données Joconde.
+
+ Sur cette plateforme, il est possible de découper des fragments d'une image et de lui assigner des mots-clés pour en faire une annotation. Les autres utilisateurs de la plateforme peuvent ensuite échanger, débattre et faire évoluer ces annotations.
+
+ Cette page décrit le vocabulaire employé sur la plateforme afin de vous aider à en comprendre le fonctionnement.
+
{{collection.description | safe}}
+ Contribuer au fond {{collection.verbose_name}} +