# HG changeset patch # User Harris Baptiste # Date 1466522154 -7200 # Node ID b3768547ad3d5659d16e307dfe6533b8f6c1a6a3 # Parent 92ffd46a5046147a4731e9ffd870c647ba1e558d# Parent f583d7af1f655e8f03e138f2355fc407fc09c81b handle merge diff -r 92ffd46a5046 -r b3768547ad3d README.md --- /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. + diff -r 92ffd46a5046 -r b3768547ad3d readme.md --- 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 -``` - diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/admin.py --- 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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/auth/urls.py --- 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) ] diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/auth/views.py --- 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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/fixtures/dev_initial_data.json --- /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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/forms/__init__.py --- 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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/forms/annotations.py --- /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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/migrations/0001_initial.py --- /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'), + ), + ] diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/migrations/__init__.py diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/models.py --- 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) - diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/settings/__init__.py --- 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', ] diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/signals/handlers.py --- 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) - - diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/annotation.html --- 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 %} - -{% endblock%} - -{% block content %} -
- -
-
- {% thumbnail annotation.image.media "100x100" crop="center" as im %} - - {% empty %} -

No image

- {% endthumbnail %} -
-
- -
- -
- - - - -
- -
- - {% thumbnail annotation.image.media "100x100" crop="center" as im %} - - {% endthumbnail %} - -

Test iri

-
- -
- -
-{% endblock %} - -{% block footer_js %} - -{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/change_annotation.html --- /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 %} + +
+ +
+
+
    +

    Type de dessin

    +
  • Rect.
  • +
  • Libre
  • +
+ +
    +

    Actions

    + +
  • Effacer
  • + +
  • Créer le fragment
  • +
+
+ +
+
+ {% thumbnail annotation.image.media "x800" crop="center" as im %} + + + {% endthumbnail %} +
+
+ +
+ +
+ +
+
+ {% thumbnail annotation.image.media "x300" crop="center" as im %} + + + + + + + + + + + + + + + + + + + + + {% endthumbnail %} +
+
    +
  • Editer le fragment
  • +
  • Afficher la zone
  • +
  • Masquer la zone
  • +
+
+ +
+
+ {% csrf_token %} + {{ form.as_p }} + + +
+
+ +
+
+ +{% endblock %} + +{% block footer_js %} + +{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/collection_home.html --- /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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/create_annotation.html --- 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 %} - -

Créer un annotation

-
-

Afficher le formulaire de création de fragment

-
- -{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/detail_annotation.html --- /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 %} +
+ +
+
+
+ {% thumbnail annotation.image.media "x300" crop="center" as im %} + + + + + + + + + + + {% endthumbnail %} +
+
+ +
+

Title: {{ annotation.current_revision.title }}

+

Description: {{ annotation.current_revision.description }}

+

Tags: {{ annotation.current_revision.tags }}

+

Fragment: {{ annotation.current_revision.fragment }}

+ + Editer | + Proposer une révision +
+
+
+ +{% endblock %} diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/detail_image.html --- /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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/edit_annotation.html --- 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 %} - -
- -
-
-
    -

    Type de dessin

    -
  • Rect.
  • -
  • Libre
  • -
- -
    -

    Actions

    - -
  • Effacer
  • - -
  • Créer le fragment
  • -
-
- -
-
- {% thumbnail annotation.image.media "x800" crop="center" as im %} - - - {% endthumbnail %} -
-
- -
- -
- -
-
- {% thumbnail annotation.image.media "x300" crop="center" as im %} - - - - - - - - - - - - - - - - - - - - - {% endthumbnail %} -
-
    -
  • Editer le fragment
  • -
  • Afficher la zone
  • -
  • Masquer la zone
  • -
-
- -
-
- {% csrf_token %} - {{ form.as_p }} - - -
-
- -
-
- -{% endblock %} - -{% block footer_js %} - -{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/home.html --- /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 diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/iconolab/show_annotation.html --- 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 %} -
- -
-
-
- {% thumbnail annotation.image.media "x300" crop="center" as im %} - - - - - - - - - - - {% endthumbnail %} -
-
- -
-

Title: {{ annotation.title }}

-

Description: {{ annotation.description }}

-

Tags: {{ annotation.tags }}

-

Fragment: {{ annotation.fragment }}

- - Editer | - Proposer une révision -
-
-
- -{% endblock %} diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/partials/header.html --- 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 @@ \ No newline at end of file + + + + \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/registration/login.html --- 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 @@

Se connecter

-
- {% csrf_token %} - -
- - -
- -
- - -
- - - -

Lost password?

- + + {% csrf_token %} + {% for field in form %} +
+ + + {% if field.errors %}{{ field.errors }}{% endif %} +
+ {% endfor %} +
-
{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/templates/registration/register.html --- /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 %} + +
+

S'enregistrer

+
+ {% csrf_token %} + {% for field in form %} +
+ + + {% if field.errors %}{{ field.errors }}{% else %}

{{ field.help_text }}

{% endif %} +
+ {% endfor %} + +
+
+ +{% endblock %} \ No newline at end of file diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/urls.py --- 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[0-9]+)/$', views.show_annotation, name='annotation_show'), - url(r'^annotation/(?P[0-9]+)/fragment', views.draw_fragment, name='annotation_fragment'), - url(r'^annotation/create', views.create_annotation, name='annotation_create'), - url(r'^annotation/(?P[0-9]+)/edit', views.edit_annotation, name='annotation_edit'), - + 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'^rest', include('restapi.urls')), - url(r'^inbox/notifications/', include(urls, namespace='notifications')), url(r'^account/', include('iconolab.auth.urls', namespace='account')), ] diff -r 92ffd46a5046 -r b3768547ad3d src/iconolab/views.py --- 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("

Home

"); +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