Added a verbose name field for collection objects, home view now gets a list of all collections
from django.db import models, transaction
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django_comments_xtd.models import XtdComment
from django.utils.text import slugify
import uuid, json, re, requests, urllib
class Tag(models.Model):
label = models.SlugField(blank=True, null=True)
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)
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()
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)
def __str__(self):
return self.name
class Item(models.Model):
collection = models.ForeignKey(Collection, related_name="items")
class ItemMetadata(models.Model):
item = models.OneToOneField('Item', related_name='metadatas')
joconde_ref = models.CharField(max_length=20, null=False, blank=False, unique=True)
domain = models.CharField(max_length=255)
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
class ImageStats(models.Model):
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)
comments_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)
class Image(models.Model):
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)
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 self.name
# # Folders
# class Folder(models.Model):
# label = models.CharField(max_length=255)
# owner = models.ForeignKey(User)
# images = models.ManyToManyField(Image)
#
# def __str__(self):
# return label
class AnnotationManager(models.Manager):
# Call Annotation.objects.create_annotation to initialize a new Annotation with its associated AnnotationStats and initial AnnotationRevision
@transaction.atomic
def create_annotation(self, author, image, title='', description='', fragment='', tags_json='[]'):
# Create annotation object
new_annotation = Annotation(
image=image,
author=author
)
new_annotation.save()
# Create initial revision
initial_revision = AnnotationRevision(
annotation=new_annotation,
author=author,
title=title,
description=description,
fragment=fragment,
state=AnnotationRevision.ACCEPTED
)
initial_revision.save()
initial_revision.set_tags(tags_json)
new_annotation.current_revision = initial_revision
new_annotation.save()
# Create stats object
new_annotation_stats = AnnotationStats(annotation=new_annotation)
new_annotation_stats.save()
new_annotation.stats = new_annotation_stats
new_annotation.save()
return new_annotation
class AnnotationStats(models.Model):
annotation = models.OneToOneField('Annotation', related_name='stats', blank=False, null=False)
submitted_revisions_count = models.IntegerField(blank=True, null=True, default=1)
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)
def contributors(self):
contributors = []
for revision in self.annotation.revisions.filter(state__in=[AnnotationRevision.ACCEPTED, AnnotationRevision.STUDIED]):
if revision.author not in contributors:
contributors.append(revision.author)
print(contributors)
return contributors
class Annotation(models.Model):
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)
author = models.ForeignKey(User, null=True)
created = models.DateTimeField(auto_now_add=True, null=True)
objects = AnnotationManager()
def update_stats(self):
pass
# Call to create a new revision, possibly from a merge
@transaction.atomic
def make_new_revision(self, author, title, description, fragment, tags_json):
if author == self.author:
# We're creating an automatically accepted revision
new_revision_state = AnnotationRevision.ACCEPTED
else:
# Revision will require validation
new_revision_state = AnnotationRevision.AWAITING
new_revision = AnnotationRevision(
annotation = self,
parent_revision=self.current_revision,
title=title,
description=description,
author=author,
fragment=fragment,
state=new_revision_state
)
new_revision.save()
new_revision.set_tags(tags_json)
if new_revision.state == AnnotationRevision.ACCEPTED:
self.current_revision = new_revision
self.save()
return new_revision
# Call when we're validating an awaiting revision whose parent is the current revision AS IT WAS CREATED
@transaction.atomic
def validate_existing_revision(self, revision_to_validate):
if revision_to_validate.parent_revision == self.current_revision and revision_to_validate.state == AnnotationRevision.AWAITING:
self.current_revision = revision_to_validate
revision_to_validate.state = AnnotationRevision.ACCEPTED
revision_to_validate.save()
self.save()
# Call to reject a
@transaction.atomic
def reject_existing_revision(self, revision_to_reject):
if revision_to_reject.state == AnnotationRevision.AWAITING:
revision_to_reject.state = AnnotationRevision.REJECTED
revision_to_reject.save()
# Call when we're validating an awaiting revision whose parent isn't the current revision OR IF IT WAS CHANGED BY THE ANNOTATION AUTHOR
@transaction.atomic
def merge_existing_revision(self, title, description, fragment, tags, revision_to_merge):
merged_revision = self.make_new_revision(author=self.author, title=title, description=description, fragment=fragment, tags=tags)
merged_revision.merge_parent_revision = revision_to_merge
merged_revision.save()
revision_to_merge.state = AnnotationRevision.STUDIED
revision_to_merge.save()
self.current_revision=merged_revision
self.save()
class AnnotationRevision(models.Model):
AWAITING = 0
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)
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'))
state = models.IntegerField(choices=REVISION_STATES, default=AWAITING)
created = models.DateTimeField(auto_now_add=True, null=True)
def set_tags(self, tags_json_string):
try:
tags_dict = json.loads(tags_json_string)
except ValueError:
pass
for tag_data in tags_dict:
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
tag_obj = Tag.objects.get(link=tag_string)
else:
tag_obj = Tag.objects.create(
link = tag_string,
)
else:
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,
description = "",
link = settings.INTERNAL_TAGS_URL+'/'+slugify(tag_string),
collection = self.annotation.image.item.collection
)
tag_info = TaggingInfo.objects.create(
tag=tag_obj,
revision=self,
accuracy = tag_accuracy,
relevancy = tag_relevancy
)
def get_tags_json(self):
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'
dbpedia_resp = requests.get(
sparql_query_url,
params={
"query": sparql_query,
"format": "json"
}
)
results = json.loads(dbpedia_resp.text).get("results", {})
variable_bindings = results.get("bindings")
if variable_bindings:
label_json = variable_bindings.pop()
else:
label_json = {"l": {"value": "ERROR_LABEL"}}
return label_json.get("l").get("value")
final_list = []
for tagging_info in self.tagginginfo_set.select_related("tag").all():
if tagging_info.tag.is_internal():
final_list.append({
"tag_label": tagging_info.tag.label,
"tag_link": tagging_info.tag.link,
"accuracy": tagging_info.accuracy,
"relevancy": tagging_info.relevancy
})
else:
tag_link = tagging_info.tag.link
#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]))
tag_label = fetch_label(tag_link, "fr", source)
final_list.append({
"tag_label": tag_label,
"tag_link": tag_link,
"accuracy": tagging_info.accuracy,
"relevancy": tagging_info.relevancy
})
except StopIteration:
pass
return json.dumps(final_list)
class IconolabComment(XtdComment):
revision = models.ForeignKey('AnnotationRevision', related_name='creation_comment', null=True, blank=True)
metacategories = models.ManyToManyField('MetaCategory', through='MetaCategoryInfo', through_fields=('comment', 'metacategory'))
class MetaCategory(models.Model):
collection = models.ForeignKey(Collection)
label = models.CharField(max_length=255)
def __str__(self):
return self.label
class MetaCategoryInfo(models.Model):
comment = models.ForeignKey('IconolabComment', on_delete=models.CASCADE)
metacategory = models.ForeignKey('MetaCategory', on_delete=models.CASCADE)
class CommentAttachement(models.Model):
LINK = 0
IMAGE = 1
PDF = 2
COMMENT_CHOICES = (
(LINK, 'link'),
(IMAGE, 'image'),
(PDF, 'pdf')
)
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)