# HG changeset patch # User durandn # Date 1466764604 -7200 # Node ID f9d4c9a63e4ea89c66908c6497edf5689c10025a # Parent 7ff344b4bf6d2a78ca83e230042168a5e53008b7 Backend work on tags (needs testing) + model changes (image_guid, Tag) diff -r 7ff344b4bf6d -r f9d4c9a63e4e README.md --- 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. diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/fixtures/dev_initial_data.json --- 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, diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/forms/annotations.py --- 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 diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/migrations/0001_initial.py --- 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')), ], ), diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/models.py --- 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) diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/settings/__init__.py --- 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/' diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/settings/dev.py.tmpl --- 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/' diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/templates/iconolab/change_annotation.html --- 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 @@
-
+ {% csrf_token %}
+ id="id_{{ form.title.name }}" value="{{ form.title.value}}">
+ id="id_{{ form.description.name }}" >{{ form.description.value}} +
+
+
+
diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/templates/iconolab/detail_annotation.html --- 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 @@

Tags: {{ annotation.current_revision.tags }}

Fragment: {{ annotation.current_revision.fragment }}

- Editer | - Proposer une révision + Editer | + Proposer une révision diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/templates/iconolab/detail_image.html --- 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 %}

detail_image for {{image_ref}} in the collection {{collection_name}}

-Create Annotation +Create Annotation {% endblock %} \ No newline at end of file diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/urls.py --- 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[a-z]+)$', views.CollectionHomepageView.as_view(), name='collection_home'), # Home fond - url(r'^collections/(?P[a-z]+)/images/(?P[a-z0-9]+)$', views.ShowImageView.as_view(), name='image_detail'), - url(r'^collections/(?P[a-z]+)/images/(?P[a-z0-9]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'), - url(r'^collections/(?P[a-z]+)/images/(?P[a-z0-9]+)/annotations/(?P[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'), - url(r'^collections/(?P[a-z]+)/images/(?P[a-z0-9]+)/annotations/(?P[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'), - url(r'^collections/(?P[a-z]+)/images/(?P[a-z0-9]+)/annotations/(?P[^/]+)/revisions/(?P[0-9]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)$', views.ShowImageView.as_view(), name='image_detail'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/revisions/(?P[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')), ] diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/utils.py --- 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 + diff -r 7ff344b4bf6d -r f9d4c9a63e4e src/iconolab/views.py --- 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):