--- a/README.md Wed Jun 22 17:53:40 2016 +0200
+++ b/README.md Fri Jun 24 12:36:44 2016 +0200
@@ -97,8 +97,8 @@
To access the loaded annotation, follow:
- /collections/ingres/images/1234567890/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/detail
- /collections/ingres/images/1234567890/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/edit
+ /collections/ingres/images/26aec320-dcfe-4cbc-b912-6a6c13e8916e/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/detail
+ /collections/ingres/images/26aec320-dcfe-4cbc-b912-6a6c13e8916e/annotations/34ae39ae-a9a2-4736-bc59-ba6f00e37f52/edit
The annotation owner is contributeur1, if you try to edit it as another user, it will create the revision but will not publish it in the current state of the project.
--- a/src/iconolab/fixtures/dev_initial_data.json Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/fixtures/dev_initial_data.json Fri Jun 24 12:36:44 2016 +0200
@@ -39,8 +39,8 @@
"model": "iconolab.Image",
"pk": 1,
"fields": {
+ "image_guid" : "26aec320-dcfe-4cbc-b912-6a6c13e8916e",
"name" : "napoleon.jpg",
- "image_ref": "1234567890",
"media" : "uploads/napoleon.jpg",
"item": 1,
"height": 1400,
--- a/src/iconolab/forms/annotations.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/forms/annotations.py Fri Jun 24 12:36:44 2016 +0200
@@ -1,6 +1,9 @@
-from django.forms import ModelForm, HiddenInput
+from django.forms import ModelForm, TypedMultipleChoiceField, HiddenInput, MultipleHiddenInput
from iconolab.models import AnnotationRevision
+import json
+class MultipleTagsField(TypedMultipleChoiceField):
+ pass
class AnnotationRevisionForm(ModelForm):
class Meta:
@@ -8,4 +11,18 @@
fields = ('title', 'description', 'fragment',)
widgets = {
'fragment': HiddenInput(),
- }
\ No newline at end of file
+ 'tags': HiddenInput()
+ }
+
+ def tags_json(self):
+ if self.instance:
+ tags_list = []
+ for tag_info in self.instance.tagginginfo_set.all():
+ tags_list.push({
+ 'tag_input': tag_info.tag.link,
+ 'relevancy': tag_info.relevancy,
+ 'accuracy': tag_info.accuracy
+ })
+ return json.dumps(tags_list)
+ else:
+ return '[]'
\ No newline at end of file
--- a/src/iconolab/migrations/0001_initial.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/migrations/0001_initial.py Fri Jun 24 12:36:44 2016 +0200
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.9.7 on 2016-06-21 12:07
+# Generated by Django 1.9.7 on 2016-06-23 15:44
from __future__ import unicode_literals
from django.conf import settings
@@ -67,9 +67,9 @@
name='Image',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('image_guid', models.UUIDField(default=uuid.uuid4, editable=False)),
('name', models.CharField(max_length=200)),
('media', models.ImageField(height_field='height', upload_to='uploads/', width_field='width')),
- ('image_ref', models.CharField(max_length=255, unique=True)),
('height', models.IntegerField()),
('width', models.IntegerField()),
('created', models.DateTimeField(auto_now_add=True, null=True)),
@@ -110,10 +110,9 @@
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('type', models.IntegerField(choices=[(0, 'dbpedia'), (1, 'iconolab')])),
- ('label', models.SlugField()),
- ('link', models.URLField(blank=True, null=True)),
- ('description', models.CharField(max_length=255)),
+ ('label', models.SlugField(blank=True, null=True)),
+ ('link', models.URLField(unique=True)),
+ ('description', models.CharField(blank=True, max_length=255, null=True)),
('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='iconolab.Collection')),
],
),
--- a/src/iconolab/models.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/models.py Fri Jun 24 12:36:44 2016 +0200
@@ -1,24 +1,19 @@
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
-import uuid
+import uuid, json
class Tag(models.Model):
- DBPEDIA = 0
- ICONOLAB = 1
- TAG_TYPES = (
- (DBPEDIA, 'dbpedia'),
- (ICONOLAB, 'iconolab')
- )
+ 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)
- type = models.IntegerField(choices=TAG_TYPES)
- label = models.SlugField()
- link = models.URLField(blank=True, null=True)
- description = models.CharField(max_length=255)
- collection = models.ForeignKey('Collection', blank=True, null=True)
+ def is_internal(self):
+ return link.startswith(settings.BASE_URL)
class TaggingInfo(models.Model):
revision = models.ForeignKey('AnnotationRevision', on_delete=models.CASCADE)
@@ -56,9 +51,9 @@
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')
- image_ref = models.CharField(max_length=255, unique=True)
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)
@@ -80,7 +75,7 @@
# 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=None):
+ def create_annotation(self, author, image, title='', description='', fragment='', tags_json='[]'):
# Create annotation object
new_annotation = Annotation(
image=image,
@@ -98,6 +93,8 @@
state=AnnotationRevision.ACCEPTED
)
initial_revision.save()
+ initial_revision.set_tags(tags_json)
+
new_annotation.current_revision = initial_revision
new_annotation.save()
@@ -197,12 +194,44 @@
author = models.ForeignKey(User, null=True)
title = models.CharField(max_length=255)
description = models.TextField(null=True)
- fragment = models.TextField() # path string
+ fragment = models.TextField()
tags = models.ManyToManyField('Tag', through='TaggingInfo', through_fields=('revision', 'tag'), blank=True, null=True)
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")
+ tag_relevancy = tag_data.get("relevancy")
+
+ if tag_string.startswith("http://"): #check if url
+ if Tag.objects.exists(link=tag_string): #check if tag already exists
+ tag_obj = Tag.objects.get(link=tag_string)
+ else:
+ tag_obj = Tag.objects.create(
+ link = settings.BASE_URL+tag_string,
+ )
+ else:
+ tag_obj = Tag.objects.create(
+ label = tag_string,
+ description = "",
+ link = settings.BASE_URL+tag_string,
+ collection = self.parent_annotation.image.item.collection
+ )
+ tag_info = TaggingInfo.objects.create(
+ tag=tag_obj,
+ revision=self,
+ accuracy = tag_accuracy,
+ relevancy = tag_relevancy
+ )
+
+
+
#class MetaCategory(models.Model):
# collection = models.ForeignKey(Collection)
# label = models.CharField(max_length=200)
--- a/src/iconolab/settings/__init__.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/settings/__init__.py Fri Jun 24 12:36:44 2016 +0200
@@ -21,6 +21,7 @@
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'media'),
]
+BASE_URL = ''
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
--- a/src/iconolab/settings/dev.py.tmpl Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/settings/dev.py.tmpl Fri Jun 24 12:36:44 2016 +0200
@@ -15,12 +15,16 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-STATIC_ROOT = os.path.join(BASE_DIR, 'web')
+STATIC_ROOT = os.path.join(BASE_DIR, '../../web/static/site')
+MEDIA_ROOT = os.path.join(BASE_DIR, '../../web/media')
+STATICFILES_DIRS = [
+ os.path.join(BASE_DIR, 'static'),
+]
+BASE_URL = ''
+STATIC_URL = '/static/'
+MEDIA_URL = '/media/'
LOGIN_URL = '/account/login/'
-#Static path
-MEDIA_ROOT = os.path.join(BASE_DIR, 'iconolab', 'media')
-
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
@@ -145,9 +149,3 @@
USE_L10N = True
USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.9/howto/static-files/
-
-STATIC_URL = '/static/'
--- a/src/iconolab/templates/iconolab/change_annotation.html Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/templates/iconolab/change_annotation.html Fri Jun 24 12:36:44 2016 +0200
@@ -71,21 +71,25 @@
</div>
<div class='col-xs-6' style="">
- <form class="form" action="{% if annotation %}{% url 'annotation_edit' collection_name image_ref annotation_guid %}{% else %}{% url 'annotation_create' collection_name image_ref %}{% endif %}" method="POST">
+ <form class="form" action="{% if annotation %}{% url 'annotation_edit' collection_name image_guid annotation_guid %}{% else %}{% url 'annotation_create' collection_name image_guid %}{% endif %}" method="POST">
{% csrf_token %}
<fieldset class="form-group">
<label class="control-label" for="id_{{ form.title.name }}">{{ form.title.label }}</label>
<input type="text" class="form-control"
name="{{ form.title.name }}"
- id="id_{{ form.title.name }}" value="{% if annotation %}{{ annotation.current_revision.title }}{% endif %}">
+ id="id_{{ form.title.name }}" value="{{ form.title.value}}">
</fieldset>
<fieldset class="form-group">
<label class="control-label" for="id_{{ form.description.name }}">{{ form.description.label }}</label>
<textarea class="form-control"
name="{{ form.description.name }}"
- id="id_{{ form.description.name }}" >{% if annotation %}{{ annotation.current_revision.description }}{% endif %}</textarea>
+ id="id_{{ form.description.name }}" >{{ form.description.value}}</textarea>
+ </fieldset>
+ <fieldset class="form-group">
+ <label class="control-label" for="id_{{ form.tags.name }}">{{ form.tags.label }}</label>
</fieldset>
<input v-model="normalizePath" type="hidden" name="fragment"></input>
+ <input id="tags_input" type="hidden" name="tags" value="{{ form.tags_json }}"></input>
<button type="submit" class="save btn btn-default">Enregister</button>
</form>
</div>
--- a/src/iconolab/templates/iconolab/detail_annotation.html Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_annotation.html Fri Jun 24 12:36:44 2016 +0200
@@ -33,8 +33,8 @@
<p> <strong>Tags:</strong> {{ annotation.current_revision.tags }}</p>
<p> <strong>Fragment:</strong> {{ annotation.current_revision.fragment }}</p>
- <a href="{% url 'annotation_edit' collection_name image_ref annotation_guid %}">Editer</a> |
- <a href="{% url 'annotation_edit' collection_name image_ref annotation_guid %}">Proposer une révision</a>
+ <a href="{% url 'annotation_edit' collection_name image_guid annotation_guid %}">Editer</a> |
+ <a href="{% url 'annotation_edit' collection_name image_guid annotation_guid %}">Proposer une révision</a>
</div>
</div>
</div>
--- a/src/iconolab/templates/iconolab/detail_image.html Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/templates/iconolab/detail_image.html Fri Jun 24 12:36:44 2016 +0200
@@ -8,6 +8,6 @@
{% block content %}
<p>detail_image for {{image_ref}} in the collection {{collection_name}}</p>
-<a href="{% url 'annotation_create' collection_name image_ref %}">Create Annotation</a>
+<a href="{% url 'annotation_create' collection_name image_guid %}">Create Annotation</a>
{% endblock %}
\ No newline at end of file
--- a/src/iconolab/urls.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/urls.py Fri Jun 24 12:36:44 2016 +0200
@@ -26,11 +26,11 @@
url(r'^admin/', admin.site.urls),
url(r'^home$', views.GlobalHomepageView.as_view(), name="home"),
url(r'^collections/(?P<collection_name>[a-z]+)$', views.CollectionHomepageView.as_view(), name='collection_home'), # Home fond
- url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)$', views.ShowImageView.as_view(), name='image_detail'),
- url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'),
- url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'),
- url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'),
- url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_ref>[a-z0-9]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[0-9]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'),
+ url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)$', views.ShowImageView.as_view(), name='image_detail'),
+ url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'),
+ url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'),
+ url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'),
+ url(r'^collections/(?P<collection_name>[a-z]+)/images/(?P<image_guid>[^/]+)/annotations/(?P<annotation_guid>[^/]+)/revisions/(?P<revision_guid>[0-9]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'),
url(r'^rest', include('restapi.urls')),
url(r'^account/', include('iconolab.auth.urls', namespace='account')),
]
--- a/src/iconolab/utils.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/utils.py Fri Jun 24 12:36:44 2016 +0200
@@ -1,35 +1,1 @@
-from django.db.models.signals import post_save
-from notifications.signals import notify
-
-class NotificationManager:
-
- NEW_ANNOTATION = 'Nouvelle annotation'
- NEW_COMMENT = 'Nouveau commentaire'
-
- class Notification:
-
- def __init__(self, sender=None, verb=None):
- self.sender = sender
- self.verb = verb
- self.recipient = None
- self.target = None
- self.description = None
-
- def set_recipient(self, recipient):
- self.recipient = recipient
-
- def set_target(self, target):
- self.target = target
-
- def set_description(self, description):
- self.description = description
-
- @classmethod
- def create_notification(self, sender=None, verb=None):
- annotation = NotificationManager.Notification(sender, verb=verb)
- return annotation
-
- @classmethod
- def notify(self, notification=None):
- #send to all users or a Group
- notify.send(notification.sender, recipient=notification.sender, verb=notification.verb)
\ No newline at end of file
+
--- a/src/iconolab/views.py Wed Jun 22 17:53:40 2016 +0200
+++ b/src/iconolab/views.py Fri Jun 24 12:36:44 2016 +0200
@@ -1,12 +1,22 @@
from django.shortcuts import HttpResponse, get_object_or_404, render
-from iconolab.models import Annotation, Collection, Image
from django.http import Http404
from django.contrib.auth.decorators import login_required
from django.views.generic import View, RedirectView
from django.views.generic.base import ContextMixin
from django.core.urlresolvers import reverse
-from .forms.annotations import AnnotationRevisionForm
-from pprint import pprint
+from iconolab.models import Annotation, Collection, Image
+from iconolab.forms.annotations import AnnotationRevisionForm
+
+def make_tags_json(annotation_revision):
+ final_list = []
+ for tagging_info in annotation_revision.tagginginfo_set.all():
+ final_list.push({
+ "tag_label": tagging_info.tag.label,
+ "tag_link": tagging_info.tag.link,
+ "accuracy": tagging_info.accuracy,
+ "relevancy": tagging_info.relevancy
+ })
+ return json.dumps(final_list)
class GlobalHomepageView(View):
def get(self, request, *args, **kwargs):
@@ -26,7 +36,7 @@
def get(self, request, *args, **kwargs):
context = super(ShowImageView, self).get_context_data(**kwargs)
context["collection_name"] = self.kwargs.get("collection_name", "")
- context["image_ref"] = self.kwargs.get("image_ref", "")
+ context["image_guid"] = self.kwargs.get("image_guid", "")
return render(request, 'iconolab/detail_image.html', context);
@@ -35,7 +45,7 @@
def get_context_data(self, **kwargs):
context = super(ShowAnnotationView, self).get_context_data(**kwargs)
context["collection_name"] = self.kwargs.get("collection_name", "")
- context["image_ref"] = self.kwargs.get("image_ref", "")
+ context["image_guid"] = self.kwargs.get("image_guid", "")
context["annotation_guid"] = self.kwargs.get("annotation_guid", "")
return context
@@ -45,7 +55,7 @@
except Collection.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
try:
- image = Image.objects.get(image_ref=kwargs.get("image_ref", ""))
+ image = Image.objects.get(image_guid=kwargs.get("image_guid", ""))
except Image.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
try:
@@ -67,7 +77,7 @@
def get_context_data(self, **kwargs):
context = super(CreateAnnotationView, self).get_context_data(**kwargs)
context["collection_name"] = self.kwargs.get("collection_name", "")
- context["image_ref"] = self.kwargs.get("image_ref", "")
+ context["image_guid"] = self.kwargs.get("image_guid", "")
return context
def check_kwargs(self, kwargs):
@@ -76,7 +86,7 @@
except Collection.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
try:
- image = Image.objects.get(image_ref=kwargs.get("image_ref", ""))
+ image = Image.objects.get(image_guid=kwargs.get("image_guid", ""))
except Image.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
return collection, image
@@ -92,22 +102,22 @@
def post(self, request, *args, **kwargs):
collection, image = self.check_kwargs(kwargs)
collection_name = kwargs["collection_name"]
- image_ref = kwargs["image_ref"]
+ image_guid = kwargs["image_guid"]
annotation_form = AnnotationRevisionForm(request.POST)
if annotation_form.is_valid():
author = request.user
title = annotation_form.cleaned_data["title"]
description = annotation_form.cleaned_data["description"]
fragment = annotation_form.cleaned_data["fragment"]
- new_annotation = Annotation.objects.create_annotation(author, image, title=title, description=description, fragment=fragment, tags=None)
- return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_ref': image_ref, 'annotation_guid': new_annotation.annotation_guid}))(request)
+ new_annotation = Annotation.objects.create_annotation(author, image, title=title, description=description, fragment=fragment)
+ return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': new_annotation.annotation_guid}))(request)
class EditAnnotationView(View, ContextMixin):
def get_context_data(self, **kwargs):
context = super(EditAnnotationView, self).get_context_data(**kwargs)
context["collection_name"] = self.kwargs.get("collection_name", "")
- context["image_ref"] = self.kwargs.get("image_ref", "")
+ context["image_guid"] = self.kwargs.get("image_guid", "")
context["annotation_guid"] = self.kwargs.get("annotation_guid", "")
return context
@@ -117,7 +127,7 @@
except Collection.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
try:
- image = Image.objects.get(image_ref=kwargs.get("image_ref", ""))
+ image = Image.objects.get(image_guid=kwargs.get("image_guid", ""))
except Image.DoesNotExist:
return RedirectView.as_view(url=reverse("404error"))
try:
@@ -128,17 +138,18 @@
def get(self, request, *args, **kwargs):
collection, image, annotation = self.check_kwargs(kwargs)
- annotation_form = AnnotationRevisionForm()
+ annotation_form = AnnotationRevisionForm(instance=annotation.current_revision)
context = self.get_context_data(**kwargs)
context["image"] = image
context["annotation"] = annotation
context["form"] = annotation_form
+ context["tags_data"] = make_tags_json(annotation.current_revision)
return render(request, 'iconolab/change_annotation.html', context)
def post(self, request, *args, **kwargs):
collection, image, annotation = self.check_kwargs(kwargs)
collection_name = kwargs["collection_name"]
- image_ref = kwargs["image_ref"]
+ image_guid = kwargs["image_guid"]
annotation_guid = kwargs["annotation_guid"]
annotation_form = AnnotationRevisionForm(request.POST)
if annotation_form.is_valid():
@@ -146,8 +157,9 @@
revision_title = annotation_form.cleaned_data["title"]
revision_description = annotation_form.cleaned_data["description"]
revision_fragment = annotation_form.cleaned_data["fragment"]
- annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, None)
- return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_ref': image_ref, 'annotation_guid': annotation_guid}))(request)
+ revision_tags_json = annotation_form.cleaned_data["tags_input"]
+ annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, revision_tags_json)
+ return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': annotation_guid}))(request)
class MergeProposalView(View):