--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,104 @@
+# How to start?
+
+1. Make sure PIP is installed then install Django and others dependencies with
+
+```
+pip install -r requirements.txt
+
+```
+
+2. Move to src/iconolab/static/js to install js dependencies.
+Make sure your have installed nodejs then run the command bellow
+
+```
+npm install
+
+```
+3. To recreate the bundle file that lives in dist/
+
+```
+npm build
+
+```
+
+4. To add a new js module, you can add it to the js/components folder and then run
+
+```
+npm start
+```
+
+## ICONOLAB ##
+
+### 1. Configuration and setup
+
+### virtualenv
+
+- Install pip
+- Create a virtualenv for the project (using virtualenvwrapper is a good idea if possible). Python version is 3.5.1
+- Run
+
+ pip install -r requirements.txt
+
+
+### node.js
+
+- Make sure nodejs is installed
+- cd into iconolab/src/iconolab/static/iconolab/js and run
+
+ npm install
+
+- To recreate the bundle file that lives in dist/
+
+ npm build
+
+- To add a new js module, you can add it to the js/components folder and then run
+
+ npm start
+
+### Django project setup
+
+- Copy iconolab/src/settings/dev.py.tmpl into iconolab/src/settings/dev.py, adapt content to configuration
+- cd into iconolab/src folder and run
+
+ python manage.py migrate
+
+to create database tables
+
+- Run
+
+ python manage.py createsuperuser
+
+to create an admin user
+
+- Run
+
+ python manage.py loaddata dev_initial_data
+
+to load the provided data fixture. This fixture will create at least one of each object used in the app. Details on the fixture data below.
+
+
+### 2. Development server
+
+- cd into the iconolab/src folder and run
+
+ python manage.py runserver
+
+By default, the app is accessible through http://127.0.0.1:8000/home
+
+
+### 3. Fixture loaded data
+
+* User: contributeur1, password: firstuser
+* User: contributeur2, password: seconduser
+
+* Collection (name): ingres
+* Image (ref): 1234567890 # You will need to move napoleon.jpg into web/media/uploads in order for the app to load the image properly
+* Annotation (guid): 34ae39ae-a9a2-4736-bc59-ba6f00e37f52
+
+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
+
+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/readme.md Tue Jun 21 17:12:21 2016 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-# How to start?
-
-1. Make sure PIP is installed then install Django and others dependencies with
-
-```
-pip install -r requirements.txt
-
-```
-
-2. Move to src/iconolab/static/js to install js dependencies.
-Make sure your have installed nodejs then run the command bellow
-
-```
-npm install
-
-```
-3. To recreate the bundle file that lives in dist/
-
-```
-npm build
-
-```
-
-4. To add a new js module, you can add it to the js/components folder and then run
-
-```
-npm start
-```
-
--- a/src/iconolab/admin.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/admin.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,25 +1,1 @@
-from django.contrib import admin
-
-# Register your models here.
-from iconolab.models import (Tag, Annotation, Collection, Image,
- Comment, CommentAttachement, MetaCategory, Activity, Notification)
-
-
-class CommentAttachmentInline(admin.TabularInline):
- model = CommentAttachement
- extra = 1
-
-
-class CommentAdmin(admin.ModelAdmin):
- inlines = [CommentAttachmentInline]
-
-
-#reversion
-admin.site.register(Image)
-admin.site.register(Collection)
-admin.site.register(Tag)
-admin.site.register(Annotation)
-admin.site.register(MetaCategory)
-admin.site.register(Comment, CommentAdmin)
-admin.site.register(Activity)
-admin.site.register(Notification)
\ No newline at end of file
+from django.contrib import admin
\ No newline at end of file
--- a/src/iconolab/auth/urls.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/auth/urls.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url
-from django.contrib.auth.views import login, password_change
+from django.contrib.auth.views import login, logout, password_change
from . import views
urlpatterns = [
- url(r'^login/', login, name='login'),
- url(r'^password/reset', password_change, name='password_reset'),
- url(r'^register', login, name='register')
- #url(r'^logout/', views.logout_view, name='logout'),
+ url(r'^register/$', views.RegisterView.as_view(), name='register'),
+ url(r'^login/$', views.LoginView.as_view(), name='login'),
+ url(r'^password/reset$', password_change, name='password_reset'),
+ url(r'^logout/', views.LogoutView.as_view(), name='logout'),
#url(r'^password/reset', view)
]
--- a/src/iconolab/auth/views.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/auth/views.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,6 +1,14 @@
-from django.contrib.auth import authenticate, login
-from django.shortcuts import redirect, render, HttpResponse
+from django.utils.http import is_safe_url
+from django.contrib.auth import authenticate, login, logout, get_user_model
+from django.contrib.auth.forms import UserCreationForm
+from django.shortcuts import redirect, render, HttpResponseRedirect
+from django.core.urlresolvers import reverse_lazy
+from django.contrib.auth.forms import AuthenticationForm
+from django.views.generic import FormView
+from django.views.generic.base import RedirectView
+from django.views.generic.edit import CreateView
+User = get_user_model()
def login_form(request):
pass
@@ -9,4 +17,51 @@
return HttpResponse('show show a login form')
def logout_view(request):
- logout(request)
\ No newline at end of file
+ logout(request)
+
+
+class LoginView(FormView):
+ template_name = "registration/login.html"
+ form_class = AuthenticationForm
+ success_url = reverse_lazy('home')
+ redirect_field_name = "next"
+
+ def get(self, request, *args, **kwargs):
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(self.success_url)
+ return super(LoginView, self).get(request, *args, **kwargs)
+
+ def form_valid(self, form):
+ login(self.request, form.get_user())
+ return super(LoginView, self).form_valid(form)
+
+ def get_success_url(self):
+ redirect_to = self.request.GET.get(self.redirect_field_name)
+ if not is_safe_url(url=redirect_to, host=self.request.get_host()):
+ redirect_to = self.success_url
+ return redirect_to
+
+class LogoutView(RedirectView):
+ url = reverse_lazy("home")
+
+ def get(self, request, *args, **kwargs):
+ logout(request)
+ return super(LogoutView, self).get(request, *args, **kwargs)
+
+class RegisterView(CreateView):
+ model = User
+ template_name = 'registration/register.html'
+ form_class = UserCreationForm
+ success_url = reverse_lazy("home")
+
+ def post(self, request, *args, **kwargs):
+ self.object = None
+ form = self.get_form()
+ if form.is_valid():
+ form.save()
+ print(request.POST)
+ user = authenticate(username=request.POST["username"], password=request.POST["password1"])
+ login(request, user)
+ return HttpResponseRedirect(self.success_url)
+ else:
+ return self.form_invalid(form)
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/fixtures/dev_initial_data.json Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,98 @@
+[
+ {
+ "model": "auth.User",
+ "pk": 1,
+ "fields": {
+ "username": "contributeur1",
+ "password": "pbkdf2_sha256$24000$4t81p9toYpfc$ufZAWaUcF51dpNqHscABIoW7UyoXxYDCRlKFI87vQJM="
+ }
+ },{
+ "model": "auth.User",
+ "pk": 2,
+ "fields": {
+ "username": "contributeur2",
+ "password": "pbkdf2_sha256$24000$b9CPP5bLU4FM$1IYIBS5y2CIEFV9jHdTdZjvj59hzx+yH5Akc5LsHo+g="
+ }
+ },{
+ "model": "iconolab.Collection",
+ "pk": 1,
+ "fields": {
+ "name": "ingres"
+ }
+ },{
+ "model": "iconolab.Item",
+ "pk": 1,
+ "fields": {
+ "collection": 1
+ }
+ },{
+ "model": "iconolab.ItemMetadata",
+ "pk": 1,
+ "fields": {
+ "item": 1,
+ "domain": "peinture",
+ "title": "Napoléon!",
+ "description": "une peinture de Napoléon",
+ "joconde_ref": "1234"
+ }
+ },{
+ "model": "iconolab.Image",
+ "pk": 1,
+ "fields": {
+ "name" : "napoleon.jpg",
+ "image_ref": "1234567890",
+ "media" : "uploads/napoleon.jpg",
+ "item": 1,
+ "height": 1400,
+ "width": 864,
+ "created": "2016-03-23 14:13:44.913765+01"
+ }
+ },{
+ "model": "iconolab.ImageStats",
+ "pk": 1,
+ "fields": {
+ "image": 1,
+ "annotations_count": 1,
+ "submitted_revisions_count": 1,
+ "comments_count": 0,
+ "folders_inclusion_count": 0,
+ "tag_count": 0
+ }
+ },{
+ "model": "iconolab.Annotation",
+ "pk": 1,
+ "fields": {
+ "annotation_guid": "34ae39ae-a9a2-4736-bc59-ba6f00e37f52",
+ "image": 1,
+ "current_revision" : 1,
+ "author": 1,
+ "created": "2016-03-24 14:13:44.913765+01"
+ }
+ },{
+ "model": "iconolab.AnnotationStats",
+ "pk": 1,
+ "fields": {
+ "annotation": 1,
+ "submitted_revisions_count": 1,
+ "accepted_revisions_count": 1,
+ "contributors_count": 1,
+ "views_count": 0,
+ "comments_count": 0,
+ "tag_count": 0
+ }
+ },{
+ "model": "iconolab.AnnotationRevision",
+ "pk": 1,
+ "fields": {
+ "annotation": 1,
+ "revision_guid": "b14c8382-b136-42d0-abe1-b0a94f60d9d0",
+ "author": 1,
+ "title": "Annotation sur Napoléon!",
+ "description": "Description exemple",
+ "fragment":"M26.720647773279353,23.625C26.720647773279353,23.625,40.48582995951417,26.375,40.48582995951417,26.375C40.48582995951417,26.375,42.71255060728745,26.125,42.71255060728745,26.125C42.71255060728745,26.125,43.11740890688259,21.625,43.11740890688259,21.625C43.11740890688259,21.625,42.10526315789473,16.875,42.10526315789473,16.875C42.10526315789473,16.875,47.16599190283401,14.375,47.16599190283401,14.375C47.16599190283401,14.375,52.63157894736842,14.5,52.63157894736842,14.5C52.63157894736842,14.5,57.28744939271255,16.5,57.28744939271255,16.5C57.28744939271255,16.5,59.10931174089069,18.875,59.10931174089069,18.875C59.10931174089069,18.875,57.48987854251012,20.75,57.48987854251012,20.75C57.48987854251012,20.75,59.311740890688256,22.625,59.311740890688256,22.625C59.311740890688256,22.625,58.502024291497975,26,58.502024291497975,26C58.502024291497975,26,65.58704453441295,27.375,65.58704453441295,27.375C65.58704453441295,27.375,72.06477732793522,27.5,72.06477732793522,27.5C72.06477732793522,27.5,80.76923076923077,13.5,80.76923076923077,13.5C80.76923076923077,13.5,85.22267206477733,13.5,85.22267206477733,13.5C85.22267206477733,13.5,82.5910931174089,16.875,82.5910931174089,16.875C82.5910931174089,16.875,79.55465587044534,22.125,79.55465587044534,22.125C79.55465587044534,22.125,75.91093117408907,26.875,75.91093117408907,26.875C75.91093117408907,26.875,76.3157894736842,30,76.3157894736842,30C76.3157894736842,30,76.3157894736842,33.125,76.3157894736842,33.125C76.3157894736842,33.125,74.2914979757085,35.375,74.2914979757085,35.375C74.2914979757085,35.375,69.4331983805668,37.375,69.4331983805668,37.375C69.4331983805668,37.375,65.1821862348178,40.125,65.1821862348178,40.125C65.1821862348178,40.125,54.453441295546554,41.375,54.453441295546554,41.375C54.453441295546554,41.375,44.534412955465584,40.5,44.534412955465584,40.5C44.534412955465584,40.5,34.61538461538461,37.375,34.61538461538461,37.375C34.61538461538461,37.375,29.757085020242915,33.625,29.757085020242915,33.625C29.757085020242915,33.625,27.327935222672064,29.5,27.327935222672064,29.5C27.327935222672064,29.5,26.720647773279353,23.625,26.720647773279353,23.625Z",
+ "state": 1,
+ "created": "2016-03-25 14:13:44.913765+01"
+ }
+ }
+
+]
\ No newline at end of file
--- a/src/iconolab/forms/__init__.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/forms/__init__.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,14 +0,0 @@
-from django.forms import ModelForm, HiddenInput
-from iconolab.models import Annotation
-
-
-class AnnotationForm(ModelForm):
- class Meta:
- model = Annotation
- fields = ('title', 'description', 'fragment',)
- widgets = {
- 'fragment': HiddenInput(),
- }
-
- def is_valid(self):
- return True
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/forms/annotations.py Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,11 @@
+from django.forms import ModelForm, HiddenInput
+from iconolab.models import AnnotationRevision
+
+
+class AnnotationRevisionForm(ModelForm):
+ class Meta:
+ model = AnnotationRevision
+ fields = ('title', 'description', 'fragment',)
+ widgets = {
+ 'fragment': HiddenInput(),
+ }
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/migrations/0001_initial.py Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-06-21 12:07
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Annotation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('annotation_guid', models.UUIDField(default=uuid.uuid4, editable=False)),
+ ('created', models.DateTimeField(auto_now_add=True, null=True)),
+ ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='AnnotationRevision',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('revision_guid', models.UUIDField(default=uuid.uuid4)),
+ ('title', models.CharField(max_length=255)),
+ ('description', models.TextField(null=True)),
+ ('fragment', models.TextField()),
+ ('state', models.IntegerField(choices=[(0, 'awaiting'), (1, 'accepted'), (2, 'rejected')], default=0)),
+ ('created', models.DateTimeField(auto_now_add=True, null=True)),
+ ('annotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='iconolab.Annotation')),
+ ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('merge_parent_revision', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reverse_merge_parent_revision', to='iconolab.AnnotationRevision')),
+ ('parent_revision', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reverse_parent_revision', to='iconolab.AnnotationRevision')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='AnnotationStats',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('submitted_revisions_count', models.IntegerField(blank=True, default=1, null=True)),
+ ('accepted_revisions_count', models.IntegerField(blank=True, default=1, null=True)),
+ ('contributors_count', models.IntegerField(blank=True, default=1, null=True)),
+ ('views_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('comments_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('tag_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('annotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='iconolab.Annotation')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Collection',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50, unique=True)),
+ ('description', models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Image',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('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)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ImageStats',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('views_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('annotations_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('submitted_revisions_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('comments_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('folders_inclusion_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('tag_count', models.IntegerField(blank=True, default=0, null=True)),
+ ('image', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='stats', to='iconolab.Image')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Item',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='iconolab.Collection')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ItemMetadata',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('joconde_ref', models.CharField(max_length=20, unique=True)),
+ ('domain', models.CharField(max_length=255)),
+ ('title', models.CharField(max_length=255)),
+ ('description', models.CharField(max_length=255)),
+ ('item', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='metadatas', to='iconolab.Item')),
+ ],
+ ),
+ migrations.CreateModel(
+ 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)),
+ ('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='iconolab.Collection')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='TaggingInfo',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('accuracy', models.IntegerField()),
+ ('relevancy', models.IntegerField()),
+ ('revision', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.AnnotationRevision')),
+ ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iconolab.Tag')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='image',
+ name='item',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='iconolab.Item'),
+ ),
+ migrations.AddField(
+ model_name='annotationrevision',
+ name='tags',
+ field=models.ManyToManyField(blank=True, null=True, through='iconolab.TaggingInfo', to='iconolab.Tag'),
+ ),
+ migrations.AddField(
+ model_name='annotation',
+ name='current_revision',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='current_for_annotation', to='iconolab.AnnotationRevision'),
+ ),
+ migrations.AddField(
+ model_name='annotation',
+ name='image',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotations', to='iconolab.Image'),
+ ),
+ migrations.AddField(
+ model_name='annotation',
+ name='source_revision',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='source_related_annotation', to='iconolab.AnnotationRevision'),
+ ),
+ ]
--- a/src/iconolab/models.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/models.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,157 +1,295 @@
-from django.db import models
+from django.db import models, transaction
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
-from reversion import revisions as reversion
-from reversion.models import Revision
+import uuid
class Tag(models.Model):
+ DBPEDIA = 0
+ ICONOLAB = 1
+ TAG_TYPES = (
+ (DBPEDIA, 'dbpedia'),
+ (ICONOLAB, 'iconolab')
+ )
+
+ type = models.IntegerField(choices=TAG_TYPES)
label = models.SlugField()
- link = models.URLField()
- description = models.TextField()
+ link = models.URLField(blank=True, null=True)
+ description = models.CharField(max_length=255)
+ collection = models.ForeignKey('Collection', blank=True, null=True)
- def __str__(self):
- return '%d:%s' % (self.id, 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()
+
# fonds Ingres - Musee de la Poste
class Collection(models.Model):
- name = models.CharField(max_length=50)
+ name = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=255)
def __str__(self):
return self.name
-# class image_metadata
+
+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 Item(models.Model):
+ collection = models.ForeignKey(Collection, related_name="items")
+
+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):
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)
- mimetype = models.CharField(null=True, blank=True, max_length=1024)
- exif = models.TextField(null=True, blank=True)
- collection = models.ForeignKey(Collection)
- date_created = models.DateTimeField(auto_now_add=True, null=True)
- date_modified = models.DateTimeField(auto_now=True, null=True)
+ 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)
+# # 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, image, author, title='', description='', fragment='', tags=None):
+ # 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()
+ 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()
+
+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):
+ # returns users that submitted an accepted revision
+ return
+
- def __str__(self):
- return label
+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):
+ 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()
+ 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 == current_revision:
+ self.current_revision = revision_to_validate
+ revision_to_validate.state = AnnotationRevision.ACCEPTED
+ revision_to_validate.save()
+ self.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()
+ self.current_revision=merged_revision
+ self.save()
-@reversion.register()
-class Annotation(models.Model):
+class AnnotationRevision(models.Model):
+
+ AWAITING = 0
+ ACCEPTED = 1
+ REJECTED = 2
+
+ REVISION_STATES = (
+ (AWAITING, 'awaiting'),
+ (ACCEPTED, 'accepted'),
+ (REJECTED, 'rejected')
+ )
+
+
+ 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='reverse_parent_revision', blank=True, null=True)
+ merge_parent_revision = models.ForeignKey('AnnotationRevision', related_name='reverse_merge_parent_revision', blank=True, null=True)
+ author = models.ForeignKey(User, null=True)
title = models.CharField(max_length=255)
description = models.TextField(null=True)
fragment = models.TextField() # path string
- tags = models.ManyToManyField(Tag)
- image = models.ForeignKey(Image, default=0, on_delete=models.CASCADE)
- author = models.ForeignKey(User, null=True)
- date_created = models.DateTimeField(auto_now_add=True, null=True)
- date_modified = models.DateTimeField(auto_now=True, null=True)
-
- def __str__(self):
- return self.title
-
-# comments
-
-LINK = 1
-IMAGE = 2
-PDF = 3
-
-COMMENT_CHOICES = (
- (LINK, 'link'),
- (IMAGE, 'image'),
- (PDF, 'pdf')
- )
-
-
-class MetaCategory(models.Model):
- collection = models.ForeignKey(Collection)
- label = models.CharField(max_length=200)
-
- def __str__(self):
- return self.label
-
- class Meta:
- verbose_name_plural = "Metacategories"
-
-class Comment(models.Model):
- author = models.ForeignKey(User)
- created_date = models.DateTimeField(blank=False, null=False, auto_now_add=True)
- annotation = models.ForeignKey(Annotation, null=True) # revision de l'utilisateur?
- target = models.ForeignKey("self") #
- #thread_id #django-contrib-comment #threadedcomment
- content = models.TextField(blank=True)
- metacategories = models.ManyToManyField(MetaCategory)
-
-
-class CommentAttachement(models.Model):
- comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
- main_annotation = models.ForeignKey(Annotation)
- attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=1)
- created_date = models.DateTimeField(auto_now_add=True)
- data = models.TextField(blank=False)
+ 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)
-# Activity & Notification
-
-class Activity(models.Model):
-
- NEW_COMMENT = 1
- NEW_REVISION = 2
- NEW_REVISION_COMMENT = 3
- NEW_EXPERT_ANSWER = 4
- NEW_REFERENCE = 5
- EXPERT_CALL = 6
-
- ACTIVITY_VERBS =(
- (NEW_COMMENT, 'Nouveau commentaire'),
- (NEW_REVISION, 'Nouvelle revision'),
- (NEW_REVISION_COMMENT, 'Nouveau commentaire de revision'),
- (NEW_EXPERT_ANSWER, 'New expert ans')
- )
-
- verb = models.IntegerField(choices=ACTIVITY_VERBS)
- actor = models.ForeignKey(User)
-
- target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
- target_object_id = models.PositiveIntegerField()
- target = GenericForeignKey('target_content_type', 'target_object_id')
+#class MetaCategory(models.Model):
+# collection = models.ForeignKey(Collection)
+# label = models.CharField(max_length=200)
+#
+# def __str__(self):
+# return self.label
+#
+# class Meta:
+# verbose_name_plural = 'Metacategories'
+#
+#
+# class Comment(models.Model):
+# author = models.ForeignKey(User)
+# created = models.DateTimeField(blank=False, null=False, auto_now_add=True)
+# annotation_revision = models.ForeignKey(AnnotationRevision, blank=True, null=True)
+# target = models.ForeignKey('Comment', blank=True, null=True)
+# content = models.TextField(blank=True)
+# metacategories = models.ManyToManyField(MetaCategory)
+#
+#
+# class CommentAttachement(models.Model):
+#
+# LINK = 0
+# IMAGE = 1
+# PDF = 2
+# COMMENT_CHOICES = (
+# (LINK, 'link'),
+# (IMAGE, 'image'),
+# (PDF, 'pdf')
+# )
+# comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
+# main_annotation = models.ForeignKey(Annotation)
+# attachment_type = models.IntegerField(choices=COMMENT_CHOICES, default=0)
+# created_date = models.DateTimeField(auto_now_add=True)
+# data = models.TextField(blank=False)
+#
+#
+# # Activity & Notification
+#
+# class Activity(models.Model):
+#
+# NEW_COMMENT = 0
+# NEW_REVISION = 1
+# NEW_COMMENT_ON_REVISION = 2
+# NEW_EXPERT_CALL = 3
+# NEW_EXPERT_ANSWER = 4
+# NEW_REFERENCE = 5
+#
+# ACTIVITY_VERBS = (
+# (NEW_COMMENT, 'New comment'),
+# (NEW_REVISION, 'New revision'),
+# (NEW_COMMENT_ON_REVISION, 'New comment on a revision'),
+# (NEW_EXPERT_CALL, 'New expert call'),
+# (NEW_EXPERT_ANSWER, 'New expert answer'),
+# (NEW_REFERENCE, 'New reference'),
+# )
+#
+# verb = models.IntegerField(choices=ACTIVITY_VERBS)
+# actor = models.ForeignKey(User)
+#
+# target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+# target_object_id = models.PositiveIntegerField()
+# target = GenericForeignKey('target_content_type', 'target_object_id')
+#
+# action_content_type = models.ForeignKey(ContentType, related_name='activity_action', on_delete=models.CASCADE, null=True, blank=True)
+# action_object_id = models.PositiveIntegerField(null=True, blank=True)
+# action_content = GenericForeignKey('action_content_type', 'action_object_id')
+#
+# created_date = models.DateTimeField(auto_now_add=True)
+#
+# def __str__(self):
+# return '%s:%s' % (author.name, verbe)
+#
+#
+# class Notification(models.Model):
+#
+# UNREAD = 0
+# READ = 1
+# DELETED = 2
+#
+# STATUS = (
+# (UNREAD, 'Unread'),
+# (READ, 'Read'),
+# (DELETED, 'Deleted')
+# )
+#
+# activity = models.ForeignKey(Activity)
+# user = models.ForeignKey(User)
+# status = models.IntegerField(choices=STATUS, default=UNREAD)
+# created_date = models.DateTimeField(auto_now_add=True)
- action_content_type = models.ForeignKey(ContentType, related_name='activity_action', on_delete=models.CASCADE, null=True, blank=True)
- action_object_id = models.PositiveIntegerField(null=True, blank=True)
- action_content = GenericForeignKey('action_content_type', 'action_object_id')
-
- created_date = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return '%s:%s' % (author.name, verbe)
-
-
-class Notification(models.Model):
-
- UNREAD = 0
- READ = 1
- DELETED = 2
-
- STATUS = (
- (READ, 'Lu'),
- (UNREAD, 'Non lu'),
- (DELETED, 'Efface')
- )
-
- activity = models.ForeignKey(Activity)
- user = models.ForeignKey(User)
- status = models.IntegerField(choices=STATUS, default=UNREAD)
- created_date = models.DateTimeField(auto_now_add=True)
-
--- a/src/iconolab/settings/__init__.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/settings/__init__.py Tue Jun 21 17:15:54 2016 +0200
@@ -71,7 +71,6 @@
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'reversion.middleware.RevisionMiddleware',
]
--- a/src/iconolab/signals/handlers.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/signals/handlers.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,32 +0,0 @@
-from iconolab.models import Comment, Annotation
-from django.db.models import signals
-from django.dispatch import receiver
-from pprint import pprint
-from notifications.signals import notify
-from iconolab.utils.utils import NotificationManager
-import pprint
-
-
-#handle metacategories here
-'''
-
-'''
-
-@receiver(signals.post_save, sender=Comment)
-def handle_metacategory(sender, **kwargs):
- commentInstance = kwargs.get('instance')
- if commentInstance is not None:
- metacategories_list = [mcat.label for mcat in commentInstance.metacategories.all()]
- print(", ".join(metacategories_list))
-
-
-@receiver(signals.post_save, sender=Annotation)
-def handle_new_annotation(sender, **kwargs):
- object_instance = kwargs.get('instance')
- if object_instance is None:
- pass
-
- notification = NotificationManager.create_notification(object_instance.author, verb=NotificationManager.NEW_ANNOTATION)
- NotificationManager.notify(notification=notification)
-
-
--- a/src/iconolab/templates/iconolab/annotation.html Tue Jun 21 17:12:21 2016 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-
-{% block page_js %}
- <script src="{% static 'iconolab/js/dist/bundle.js' %}" type="text/javascript"></script>
-{% endblock%}
-
-{% block content %}
- <div>
-
- <div class="col-md-4">
- <div id="fragmentContainer">
- {% thumbnail annotation.image.media "100x100" crop="center" as im %}
- <img src=" {{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
- {% empty %}
- <p>No image</p>
- {% endthumbnail %}
- </div>
- </div>
-
- <div class="col-md-4">
-
- <div id='iconolab-image' style='position:relative'>
- <img src="{% static annotation.image.media.url %}"
- width="{{ annotation.image.width }}"
- height="{{ annotation.image.height }}" />
- <!-- deal with path here -->
- <svg class='cut-canvas' style='border: 1px solid red;'></svg>
- <path class='image-path' d='M150 0 L75 200 L225 200 Z' />
- </div>
-
- <div sytle='border: 1px solid red; width: 300px; height:500px'>
-
- {% thumbnail annotation.image.media "100x100" crop="center" as im %}
- <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
- {% endthumbnail %}
-
- <p>Test iri</p>
- </div>
-
- </div>
-
- </div>
-{% endblock %}
-
-{% block footer_js %}
- <script>
- iconolab.initCutoutComponent({ imageId: '#iconolab-image' });
- </script>
-{% endblock %}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/change_annotation.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,173 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% block content %}
+
+ <div id="drawing-zone" class="row" style="padding-top: 10px; border:1px solid orange">
+
+ <div v-show='!formView' class="editor-wrapper col-md-12">
+ <div class='col-md-2'>
+ <ul class="form-drawing-wrapper list-inline">
+ <p class='form-drawing pullright'><strong>Type de dessin</strong></p>
+ <li @click="setDrawingMode('RECT')" v-bind:class="{ 'selected': isRect }" class='pull-md-right drawingModeBtn'>Rect.</li>
+ <li @click="setDrawingMode('FREE')" v-bind:class="{ 'selected': !isRect }" class='pull-md-right drawingModeBtn'>Libre</li>
+ </ul>
+
+ <ul class='form-drawing-wrapper list-inline'>
+ <p class='form-drawing pullright'><strong>Actions</strong></p>
+
+ <li @click="clear" class='pull-md-right drawingModeBtn'><i class='fa fa-trash'></i> Effacer</li>
+
+ <li @click="save" class='pull-md-right drawingModeBtn'><i class='fa fa-plus'></i> Créer le fragment</li>
+ </ul>
+ </div>
+
+ <div class="col-md-8">
+ <div v-el:image id="iconolab-image-wrapper">
+ {% thumbnail annotation.image.media "x800" crop="center" as im %}
+ <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+ <path class="image-path" d="{{ annotation.current_revision.fragment }}"></path>
+ {% endthumbnail %}
+ </div>
+ </div>
+
+ </div>
+
+ <div v-show="formView" class="col-md-12">
+
+ <div class="col-xs-6">
+ <div class="small-image-wrapper" style="position: relative">
+ {% thumbnail annotation.image.media "x300" crop="center" as im %}
+ <img v-el:small-image @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
+
+ <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
+
+ <defs>
+ <mask xmlns="http://www.w3.org/2000/svg" id="smallImage">
+ <rect x="0" y="0" width="{{ im.width }}", height="{{ im.height }}" fill="white"/>
+ <g v-bind:transform="transformMatrix">
+ <path v-bind:d="fragmentPath"></path>
+ </g>
+ </mask>
+ </defs>
+
+ <g v-show="!displayMask" v-bind:transform="transformMatrix">
+ <path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
+ </g>
+
+ <rect v-show="displayMask" v-el:small-mask x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
+ </svg>
+
+ {% endthumbnail %}
+ </div>
+ <ul class="inline">
+ <li @click="showEditor" class="showPointer"> <i class='fa fa-edit'></i> Editer le fragment</li>
+ <li v-show="!displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Afficher la zone</li>
+ <li v-show="displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Masquer la zone</li>
+ </ul>
+ </div>
+
+ <div class='col-xs-6' style="">
+ <form method="POST" class="post-form">
+ {% csrf_token %}
+ {{ form.as_p }}
+ <input v-model="normalizePath" type="hidden" name="fragment"></input>
+ <button type="submit" class="save btn btn-default">Enregister</button>
+ </form>
+ </div>
+
+ </div>
+ </div>
+
+{% endblock %}
+
+{% block footer_js %}
+ <script>
+ var drawingVue = new Vue({
+ el: '#drawing-zone',
+ MODE_RECT: 'RECT',
+ MODE_FREE: 'FREE',
+
+ data: {
+ mode:"",
+ isRect: true,
+ normalizePath: "",
+ readOnly: false,
+ formView: false,
+ useClipPath: false,
+ transformMatrix: "",
+ fragmentPath: "",
+ displayMask: false
+
+ },
+
+ init: function () {
+ var self = this;
+ this.drawingComponent = iconolab.initCutoutComponent({
+ wrapperId: '#iconolab-image-wrapper',
+ actionWrapper: '#action-wrapper',
+ readOnly: false,
+ onDrawingModeChange: function (mode) {
+ self.$nextTick(function () {
+ self.setDrawingMode(mode, false);
+ });
+ }
+ });
+ },
+
+ methods: {
+
+ setDrawingMode: function (mode, updateComponent) {
+ var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
+ this.mode = this.$options['MODE_' + mode];
+ this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
+ if (updateComponent) {
+ this.drawingComponent.setDrawingMode(this.mode);
+ }
+ },
+
+ showEditor: function () {
+ this.formView = false;
+ console.log(this.$els.smallImage);
+ },
+
+ highLightZone: function () {
+ if (!this.displayMask) {
+ this.displayMask = true;
+ }
+ else {
+ this.displayMask = false;
+ }
+ //this.maskFill = "orange";
+ //this.fragmentFill = "none";
+ },
+
+ displayEditedPath: function () {
+ /* path to save */
+ var normalizePath = this.drawingComponent.getPath();
+ },
+
+ save: function () {
+ this.normalizePath = this.drawingComponent.getPath();
+ var smallImage = this.$els.smallImage;
+ this.formView = true;
+ /* 100x = smallImageHeight && 100x=smallImageWidth | 100 = ViewBoxBound */
+ var xRatio = smallImage.width / 100;
+ var yRatio = smallImage.height / 100;
+ var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
+ this.transformMatrix ="matrix(" + transformMatrix + ")";
+ this.fragmentPath = this.normalizePath.split(';')[0];
+ console.log(this.fragmentPath);
+ console.log(this.transformMatrix);
+ },
+
+ clear: function () {
+ this.drawingComponent.clear();
+ }
+ }
+ });
+ </script>
+{% endblock %}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/collection_home.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}collectionhome{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/create_annotation.html Tue Jun 21 17:12:21 2016 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-{% extends 'iconolab_base.html'%}
-
-{% block content %}
-
-<h2>Créer un annotation</h2>
-<div class='col-md-8'>
- <p> Afficher le formulaire de création de fragment </p>
-</div>
-
-{% endblock %}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/detail_annotation.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,42 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}
+ <div id="annotation-wrapper" class="row" style="border: 1px solid gray">
+
+ <div v-show="formView" class="col-md-12">
+ <div class="col-xs-6">
+ <div class="small-image-wrapper" style="position: relative">
+ {% thumbnail annotation.image.media "x300" crop="center" as im %}
+ <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
+
+ <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
+
+ <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
+ <path d="{{ annotation.current_revision.fragment }}" opacity="0.7" fill="orange"></path>
+ </g>
+
+ </svg>
+
+ {% endthumbnail %}
+ </div>
+ </div>
+
+ <div class='col-xs-6' style="">
+ <p> <strong>Title:</strong> {{ annotation.current_revision.title }}</p>
+ <p> <strong>Description:</strong> {{ annotation.current_revision.description }}</p>
+ <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>
+ </div>
+ </div>
+ </div>
+
+{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/detail_image.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}detailimage{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/edit_annotation.html Tue Jun 21 17:12:21 2016 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-{% block content %}
-
- <div id="drawing-zone" class="row" style="padding-top: 10px; border:1px solid orange">
-
- <div v-show='!formView' class="editor-wrapper col-md-12">
- <div class='col-md-2'>
- <ul class="form-drawing-wrapper list-inline">
- <p class='form-drawing pullright'><strong>Type de dessin</strong></p>
- <li @click="setDrawingMode('RECT')" v-bind:class="{ 'selected': isRect }" class='pull-md-right drawingModeBtn'>Rect.</li>
- <li @click="setDrawingMode('FREE')" v-bind:class="{ 'selected': !isRect }" class='pull-md-right drawingModeBtn'>Libre</li>
- </ul>
-
- <ul class='form-drawing-wrapper list-inline'>
- <p class='form-drawing pullright'><strong>Actions</strong></p>
-
- <li @click="clear" class='pull-md-right drawingModeBtn'><i class='fa fa-trash'></i> Effacer</li>
-
- <li @click="save" class='pull-md-right drawingModeBtn'><i class='fa fa-plus'></i> Créer le fragment</li>
- </ul>
- </div>
-
- <div class="col-md-8">
- <div v-el:image id="iconolab-image-wrapper">
- {% thumbnail annotation.image.media "x800" crop="center" as im %}
- <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
- <path class="image-path" d="{{ annotation.fragment }}"></path>
- {% endthumbnail %}
- </div>
- </div>
-
- </div>
-
- <div v-show="formView" class="col-md-12">
-
- <div class="col-xs-6">
- <div class="small-image-wrapper" style="position: relative">
- {% thumbnail annotation.image.media "x300" crop="center" as im %}
- <img v-el:small-image @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-
- <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
-
- <defs>
- <mask xmlns="http://www.w3.org/2000/svg" id="smallImage">
- <rect x="0" y="0" width="{{ im.width }}", height="{{ im.height }}" fill="white"/>
- <g v-bind:transform="transformMatrix">
- <path v-bind:d="fragmentPath"></path>
- </g>
- </mask>
- </defs>
-
- <g v-show="!displayMask" v-bind:transform="transformMatrix">
- <path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
- </g>
-
- <rect v-show="displayMask" v-el:small-mask x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
- </svg>
-
- {% endthumbnail %}
- </div>
- <ul class="inline">
- <li @click="showEditor" class="showPointer"> <i class='fa fa-edit'></i> Editer le fragment</li>
- <li v-show="!displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Afficher la zone</li>
- <li v-show="displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Masquer la zone</li>
- </ul>
- </div>
-
- <div class='col-xs-6' style="">
- <form method="POST" class="post-form">
- {% csrf_token %}
- {{ form.as_p }}
- <input v-model="normalizePath" type="hidden" name="fragment"></input>
- <button type="submit" class="save btn btn-default">Enregister</button>
- </form>
- </div>
-
- </div>
- </div>
-
-{% endblock %}
-
-{% block footer_js %}
- <script>
- var drawingVue = new Vue({
- el: '#drawing-zone',
- MODE_RECT: 'RECT',
- MODE_FREE: 'FREE',
-
- data: {
- mode:"",
- isRect: true,
- normalizePath: "",
- readOnly: false,
- formView: false,
- useClipPath: false,
- transformMatrix: "",
- fragmentPath: "",
- displayMask: false
-
- },
-
- init: function () {
- var self = this;
- this.drawingComponent = iconolab.initCutoutComponent({
- wrapperId: '#iconolab-image-wrapper',
- actionWrapper: '#action-wrapper',
- readOnly: false,
- onDrawingModeChange: function (mode) {
- self.$nextTick(function () {
- self.setDrawingMode(mode, false);
- });
- }
- });
- },
-
- methods: {
-
- setDrawingMode: function (mode, updateComponent) {
- var updateComponent = (typeof updateComponent === "boolean") ? updateComponent: true;
- this.mode = this.$options['MODE_' + mode];
- this.isRect = (this.mode === this.$options.MODE_RECT) ? true: false;
- if (updateComponent) {
- this.drawingComponent.setDrawingMode(this.mode);
- }
- },
-
- showEditor: function () {
- this.formView = false;
- console.log(this.$els.smallImage);
- },
-
- highLightZone: function () {
- if (!this.displayMask) {
- this.displayMask = true;
- }
- else {
- this.displayMask = false;
- }
- //this.maskFill = "orange";
- //this.fragmentFill = "none";
- },
-
- displayEditedPath: function () {
- /* path to save */
- var normalizePath = this.drawingComponent.getPath();
- },
-
- save: function () {
- this.normalizePath = this.drawingComponent.getPath();
- var smallImage = this.$els.smallImage;
- this.formView = true;
- /* 100x = smallImageHeight && 100x=smallImageWidth | 100 = ViewBoxBound */
- var xRatio = smallImage.width / 100;
- var yRatio = smallImage.height / 100;
- var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
- this.transformMatrix ="matrix(" + transformMatrix + ")";
- this.fragmentPath = this.normalizePath.split(';')[0];
- console.log(this.fragmentPath);
- console.log(this.transformMatrix);
- },
-
- clear: function () {
- this.drawingComponent.clear();
- }
- }
- });
- </script>
-{% endblock %}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/iconolab/home.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,9 @@
+{% extends 'iconolab_base.html' %}
+
+{% load staticfiles %}
+
+{% load thumbnail %}
+
+{% load iconolab_tags %}
+
+{% block content %}globalhome{% endblock %}
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/show_annotation.html Tue Jun 21 17:12:21 2016 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-{% extends 'iconolab_base.html' %}
-
-{% load staticfiles %}
-
-{% load thumbnail %}
-
-{% load iconolab_tags %}
-
-{% block content %}
- <div id="annotation-wrapper" class="row" style="border: 1px solid gray">
-
- <div v-show="formView" class="col-md-12">
- <div class="col-xs-6">
- <div class="small-image-wrapper" style="position: relative">
- {% thumbnail annotation.image.media "x300" crop="center" as im %}
- <img v-el:small-image src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
-
- <svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
-
- <g transform="matrix({% transform_matrix im_width=im.width im_height=im.height max_x=100 max_y=100 %})">
- <path d="{{ annotation.fragment }}" opacity="0.7" fill="orange"></path>
- </g>
-
- </svg>
-
- {% endthumbnail %}
- </div>
- </div>
-
- <div class='col-xs-6' style="">
- <p> <strong>Title:</strong> {{ annotation.title }}</p>
- <p> <strong>Description:</strong> {{ annotation.description }}</p>
- <p> <strong>Tags:</strong> {{ annotation.tags }}</p>
- <p> <strong>Fragment:</strong> {{ annotation.fragment }}</p>
-
- <a href="{% url 'annotation_edit' annotation.id %}">Editer</a> |
- <a href="{% url 'annotation_edit' annotation.id %}">Proposer une révision</a>
- </div>
- </div>
- </div>
-
-{% endblock %}
--- a/src/iconolab/templates/partials/header.html Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/templates/partials/header.html Tue Jun 21 17:15:54 2016 +0200
@@ -1,33 +1,38 @@
<nav class="navbar navbar-default" style="">
- <div class="container-fluid">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
- <span class="sr-only">{{ greeting }}</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="navbar-brand" href="#">Iconolab</a>
- </div>
- <div id="navbar" class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li class="active"><a href="#">Accueil</a></li>
- <li><a href="#">Le projet</a></li>
- <li><a href="#">Contribuer</a></li>
- </ul>
-
- <form class="navbar-form navbar-left" role="search">
- <div class="form-group">
- <input type="text" class="form-control" placeholder="Trouver une image...">
- </div>
- <button type="submit" class="btn btn-default">Rechercher</button>
- </form>
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">{{ greeting }}</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#">Iconolab</a>
+ </div>
+ <div id="navbar" class="navbar-collapse collapse">
+ <ul class="nav navbar-nav">
+ <li class="active"><a href="#">Accueil</a></li>
+ <li><a href="#">Le projet</a></li>
+ <li><a href="#">Contribuer</a></li>
+ </ul>
+
+ <form class="navbar-form navbar-left" role="search">
+ <div class="form-group">
+ <input type="text" class="form-control" placeholder="Trouver une image...">
+ </div>
+ <button type="submit" class="btn btn-default">Rechercher</button>
+ </form>
- <ul class="nav navbar-nav navbar-right">
- <li role="presentation"><a href="#">Notifications <span class="badge">3</span></a></li>
- <li><a href="{% url 'account:register' %}">Créer un compte</a></li>
- <li><a href="{% url 'account:login' %}">Se Connecter</a></li>
- </ul>
- </div><!--/.nav-collapse -->
- </div><!--/.container-fluid -->
- </nav>
\ No newline at end of file
+ <ul class="nav navbar-nav navbar-right">
+ <li role="presentation"><a href="#">Notifications <span class="badge">3</span></a></li>
+ {% if user.is_authenticated %}
+ <li><a href="#">{{user.username}}: Mon espace</a></li>
+ <li><a href="{% url 'account:logout' %}">Se déconnecter</a></li>
+ {% else %}
+ <li><a href="{% url 'account:register' %}">Créer un compte</a></li>
+ <li><a href="{% url 'account:login' %}">Se Connecter</a></li>
+ {% endif %}
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div><!--/.container-fluid -->
+</nav>
\ No newline at end of file
--- a/src/iconolab/templates/registration/login.html Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/templates/registration/login.html Tue Jun 21 17:15:54 2016 +0200
@@ -17,25 +17,19 @@
<div class='col-md-6 center centerer'>
<h3>Se connecter</h3>
- <form method="post" action="{% url 'account:login' %}">
- {% csrf_token %}
-
- <div class="form-group">
- <label for="exampleInputEmail1">{{form.username.label_tag}}</label>
- <input type="text" class="form-control" id="username" placeholder="Login ou utilisateur">
- </div>
-
- <div class="form-group">
- <label for="exampleInputPassword1">{{form.password.label_tag}}</label>
- <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
- </div>
-
- <input type="hidden" name="next" value="{{ next }}" />
- <button type="submit" class="btn btn-default">Submit</button>
- <p><a href="{% url 'account:password_reset' %}">Lost password?</a></p>
-
+ <form class="form" action="{% url 'account:login' %}" method="POST">
+ {% csrf_token %}
+ {% for field in form %}
+ <fieldset class="form-group">
+ <label class="control-label" for="id_{{ field.name }}">{{ field.label }}</label>
+ <input type="{% if field.label == "Username" %}text{% else %}password{% endif %}" class="form-control"
+ name="{{ field.name }}"
+ id="id_{{ field.name }}" >
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ </fieldset>
+ {% endfor %}
+ <input type="submit" value="Submit" class="btn btn-primary">
</form>
-
</div>
{% endblock %}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/templates/registration/register.html Tue Jun 21 17:15:54 2016 +0200
@@ -0,0 +1,22 @@
+{% extends "iconolab_base.html" %}
+
+{% block content %}
+
+<div class='col-md-6 center centerer'>
+ <h3>S'enregistrer</h3>
+ <form class="form" action="{% url 'account:register' %}" method="POST">
+ {% csrf_token %}
+ {% for field in form %}
+ <fieldset class="form-group">
+ <label class="control-label" for="id_{{ field.name }}">{{ field.label }}</label>
+ <input type="{% if field.label == "Username" %}text{% else %}password{% endif %}" class="form-control"
+ name="{{ field.name }}"
+ id="id_{{ field.name }}" >
+ {% if field.errors %}{{ field.errors }}{% else %}<p class="help-text">{{ field.help_text }}</p>{% endif %}
+ </fieldset>
+ {% endfor %}
+ <input type="submit" value="Submit" class="btn btn-primary">
+ </form>
+</div>
+
+{% endblock %}
\ No newline at end of file
--- a/src/iconolab/urls.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/urls.py Tue Jun 21 17:15:54 2016 +0200
@@ -15,23 +15,23 @@
"""
from django.conf.urls import url, include
from django.contrib import admin
-from notifications import urls
from . import views
from . import settings
from django.conf.urls.static import static
+from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns = [
url(r'^admin/', admin.site.urls),
- url(r'^home', views.index, name="home"),
- url(r'^annotation/(?P<pk>[0-9]+)/$', views.show_annotation, name='annotation_show'),
- url(r'^annotation/(?P<pk>[0-9]+)/fragment', views.draw_fragment, name='annotation_fragment'),
- url(r'^annotation/create', views.create_annotation, name='annotation_create'),
- url(r'^annotation/(?P<pk>[0-9]+)/edit', views.edit_annotation, name='annotation_edit'),
-
+ 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'^rest', include('restapi.urls')),
- url(r'^inbox/notifications/', include(urls, namespace='notifications')),
url(r'^account/', include('iconolab.auth.urls', namespace='account')),
]
--- a/src/iconolab/views.py Tue Jun 21 17:12:21 2016 +0200
+++ b/src/iconolab/views.py Tue Jun 21 17:15:54 2016 +0200
@@ -1,46 +1,128 @@
-from django.shortcuts import HttpResponse, get_object_or_404, render, redirect
-from iconolab.models import Annotation
+from django.shortcuts import HttpResponse, get_object_or_404, render
+from iconolab.models import Annotation, Collection
from django.http import Http404
from django.contrib.auth.decorators import login_required
-from .forms import AnnotationForm
+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
-@login_required
-def index(request):
- print(request.user)
- return HttpResponse("<p>Home</p>");
+class GlobalHomepageView(View):
+ def get(self, request, *args, **kwargs):
+ # Handle homepage view here
+ return render(request, 'iconolab/home.html');
+
+
+class CollectionHomepageView(View, ContextMixin):
+ def get(self, request, *args, **kwargs):
+ context = super(CollectionHomepageView, self).get_context_data(**kwargs)
+ context["collection_name"] = self.kwargs.get("collection_name", "")
+ return render(request, 'iconolab/collection_home.html', context);
+
+
+class ShowImageView(View):
+ def get(self, request, *args, **kwargs):
+ context = super(CollectionHomepageView, self).get_context_data(**kwargs)
+ context["collection_name"] = self.kwargs.get("collection_name", "")
+ context["image_ref"] = self.kwargs.get("image_ref", "")
+ return render(request, 'iconolab/collection_home.html', context);
-def draw_fragment(request, pk):
- annotation = get_object_or_404(Annotation, pk=pk)
- return render(request, 'iconolab/fragment_draw.html', {'annotation': annotation})
+class ShowAnnotationView(View, ContextMixin):
+
+ 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["annotation_guid"] = self.kwargs.get("annotation_guid", "")
+ return context
+
+ def check_kwargs(self, kwargs):
+ try:
+ collection = Collection.objects.get(name=kwargs.get("collection_name", ""))
+ except Collection.DoesNotExist:
+ return RedirectView.as_view(url=reverse("404error"))
+ try:
+ annotation = Annotation.objects.get(annotation_guid=kwargs.get("annotation_guid", ""))
+ except Annotation.DoesNotExist:
+ return RedirectView.as_view(url=reverse("404error"))
+ return collection, annotation
+
+ def get(self, request, *args, **kwargs):
+ collection, annotation = self.check_kwargs(kwargs)
+ self.check_kwargs(kwargs)
+ context = self.get_context_data(**kwargs)
+ context["annotation"] = annotation
+ print(annotation)
+ return render(request, 'iconolab/detail_annotation.html', context)
-def create_annotation(request):
- return render(request, 'iconolab/create_annotation.html')
- pass
-
-def show_annotation(request, pk):
- annotation = get_object_or_404(Annotation, pk=pk)
- return render(request, 'iconolab/show_annotation.html', {'annotation': annotation})
+
+class CreateAnnotationView(View):
+
+ def get(self, request, *args, **kwargs):
+ print("test")
+ return render(request, 'iconolab/change_annotation.html')
+
+ def post(self, request, *args, **kwargs):
+ # Handle annotation submit here
+ pass
+
+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["annotation_guid"] = self.kwargs.get("annotation_guid", "")
+ return context
+
+ def check_kwargs(self, kwargs):
+ try:
+ collection = Collection.objects.get(name=kwargs.get("collection_name", ""))
+ except Collection.DoesNotExist:
+ return RedirectView.as_view(url=reverse("404error"))
+ try:
+ annotation = Annotation.objects.get(annotation_guid=kwargs.get("annotation_guid", ""))
+ except Annotation.DoesNotExist:
+ return RedirectView.as_view(url=reverse("404error"))
+ return collection, annotation
+
+ def get(self, request, *args, **kwargs):
+ collection, annotation = self.check_kwargs(kwargs)
+ annotation_form = AnnotationRevisionForm()
+ context = self.get_context_data(**kwargs)
+ context["annotation"] = annotation
+ context["form"] = annotation_form
+ return render(request, 'iconolab/change_annotation.html', context)
+
+ def post(self, request, *args, **kwargs):
+ collection, annotation = self.check_kwargs(kwargs)
+ collection_name = kwargs["collection_name"]
+ image_ref = kwargs["image_ref"]
+ annotation_guid = kwargs["annotation_guid"]
+ annotation_form = AnnotationRevisionForm(request.POST)
+ if annotation_form.is_valid():
+ revision_author = request.user
+ 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)
-# save annotation
-# handle revision
-def save_annotation(request):
- pass
-
-def edit_annotation(request, pk):
-
- #enregistrer title, description, fragment:nmz
- annotation = get_object_or_404(Annotation, pk=pk)
-
- if request.method == 'POST':
- annotation_form = AnnotationForm(request.POST, instance=annotation)
- if annotation_form.is_valid():
- annotation = annotation_form.save()
- return redirect('iconolab.views.show_annotation', pk=pk)
- else:
- Annotation_form = AnnotationForm(instance = annotation)
-
- return render(request, 'iconolab/edit_annotation.html', {'annotation': annotation, 'form': Annotation_form })
-
\ No newline at end of file
+class MergeProposalView(View):
+
+ def get(self, request, *args, **kwargs):
+ # Handle merge form display here
+ pass
+
+ def post(self, request, *args, **kwargs):
+ # Handle merge form submit here
+ pass
+
+
+class NotFoundErrorView(View):
+ def get(self, request, *args, **kwargs):
+ # Handle image display here
+ pass
\ No newline at end of file