Gitlab CSE Unil

Commit d79601ad authored by Julien Furrer's avatar Julien Furrer
Browse files

Added Usernames management and shared annotations display

parent e40bbbe5
...@@ -14,4 +14,7 @@ adim_project/apache.conf ...@@ -14,4 +14,7 @@ adim_project/apache.conf
adim_project/wsgi.py adim_project/wsgi.py
adim_project/settings/local_settings.py adim_project/settings/local_settings.py
adim_project/apache.conf.tpl adim_project/apache.conf.tpl
\ No newline at end of file adim_app/playground.py
adim_app/templates/playground
adim_app/static/js/playground.js
\ No newline at end of file
# coding=utf-8 # coding=utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import user
import uuid, random import uuid, random
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
...@@ -124,6 +125,7 @@ class AnObj(BaseEntity): ...@@ -124,6 +125,7 @@ class AnObj(BaseEntity):
self._set_uuid(no_save=True) self._set_uuid(no_save=True)
super(AnObj, self).save(*args, **kwargs) super(AnObj, self).save(*args, **kwargs)
# if not self._thumb_url: # if not self._thumb_url:
# self._thumb_url = self._create_thumbnail() # self._thumb_url = self._create_thumbnail()
# self.save() # self.save()
...@@ -143,9 +145,11 @@ class AnObj(BaseEntity): ...@@ -143,9 +145,11 @@ class AnObj(BaseEntity):
publish_mode = kwargs.get('publish_mode', 0) publish_mode = kwargs.get('publish_mode', 0)
memberships = [] memberships = []
for user in args: for user in args:
memberships.append(AnObjMembership(anobj=self, user=user, publish_mode=publish_mode)) AnObjMembership.objects.get_or_create(anobj=self, user=user, defaults={'publish_mode': publish_mode})
if memberships: #
AnObjMembership.objects.bulk_create(memberships) # memberships.append(AnObjMembership(anobj=self, user=user, publish_mode=publish_mode))
# if memberships:
# AnObjMembership.objects.bulk_create(memberships)
def remove_members(self, *args): def remove_members(self, *args):
""" """
......
...@@ -103,7 +103,7 @@ class AnObjSerializer(BaseAnObjSerializer): ...@@ -103,7 +103,7 @@ class AnObjSerializer(BaseAnObjSerializer):
class Meta: class Meta:
model = AnObj model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'ao_type', 'annotations', 'members', fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'annotations', 'members',
'sharing_mode', 'sharing_opts', 'locked', 'allow_public_publishing') 'sharing_mode', 'sharing_opts', 'locked', 'allow_public_publishing')
...@@ -111,11 +111,11 @@ class SharedAnObjSerializer(BaseAnObjSerializer): ...@@ -111,11 +111,11 @@ class SharedAnObjSerializer(BaseAnObjSerializer):
class Meta: class Meta:
model = AnObj model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'ao_type', 'annotations', 'locked', fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'annotations', 'locked',
'sharing_mode', 'allow_public_publishing') 'sharing_mode', 'allow_public_publishing')
class AnObjListSerializer(BaseAnObjSerializer): class AnObjListSerializer(BaseAnObjSerializer):
class Meta: class Meta:
model = AnObj model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'locked', 'sharing_mode', 'ao_type') fields = ('id', 'uuid', 'name', 'owner', 'locked', 'sharing_mode')
\ No newline at end of file \ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.http.response import HttpResponseForbidden, Http404 from django.http.response import HttpResponseForbidden, Http404
from django.conf import settings from django.conf import settings
from django.db.models import get_model, Q from django.db.models import get_model, Q
...@@ -13,7 +14,7 @@ from adim.serializers import AnObjSerializer, SharedAnObjSerializer, AnObjListSe ...@@ -13,7 +14,7 @@ from adim.serializers import AnObjSerializer, SharedAnObjSerializer, AnObjListSe
from adim.permissions import has_anobj_access from adim.permissions import has_anobj_access
from rest_framework import generics, viewsets from rest_framework import generics, viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import detail_route from rest_framework.decorators import detail_route, list_route
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.parsers import FileUploadParser from rest_framework.parsers import FileUploadParser
...@@ -49,44 +50,72 @@ class AnObjViewSet(viewsets.ModelViewSet): ...@@ -49,44 +50,72 @@ class AnObjViewSet(viewsets.ModelViewSet):
if self.action == 'list': if self.action == 'list':
q = q | Q(members=user) q = q | Q(members=user)
return AnObj.objects.filter(q) return AnObj.objects.filter(q).distinct()
@detail_route(methods=['patch', 'post', 'delete']) @detail_route(methods=['get', 'patch', 'post', 'delete'])
def members(self, request, pk=None): def members(self, request, pk=None):
anobj = self.get_object() anobj = self.get_object()
user_model = get_model(*settings.AUTH_USER_MODEL.split('.')) if request.method != 'GET':
members = [] user_model = get_model(*settings.AUTH_USER_MODEL.split('.'))
for member_data in request.DATA.get('members', []): members = []
try: for member_data in request.DATA.get('members', []):
q = Q(pk=-1) try:
if member_data.get('id'): q = Q(pk=-1)
q = Q(pk=member_data.get('id')) if member_data.get('id'):
elif member_data.get('username'): q = Q(pk=member_data.get('id'))
q = Q(username=member_data.get('username')) elif member_data.get('username'):
members.append(user_model.objects.get(q)) q = Q(username=member_data.get('username'))
members.append(user_model.objects.get(q))
except user_model.DoesNotExist:
if member_data.get('username'): except user_model.DoesNotExist:
opts_members = anobj.sharing_opts.get('members', []) if member_data.get('username'):
opts_members = anobj.sharing_opts.get('members', [])
if request.method in ('PATCH', 'POST'):
opts_members.append(member_data.get('username')) if request.method in ('PATCH', 'POST'):
elif request.method == 'DELETE': opts_members.append(member_data.get('username'))
opts_members.remove(member_data.get('username')) elif request.method == 'DELETE':
opts_members.remove(member_data.get('username'))
anobj.sharing_opts['members'] = opts_members
anobj.save() anobj.sharing_opts['members'] = opts_members
anobj.save()
if request.method in ('PATCH', 'POST'):
# anobj.members.add(*members) if request.method in ('PATCH', 'POST'):
anobj.add_members(*members) # anobj.members.add(*members)
elif request.method == 'DELETE': anobj.add_members(*members)
# anobj.members.remove(*members) elif request.method == 'DELETE':
anobj.remove_members(*members) # anobj.members.remove(*members)
anobj.remove_members(*members)
return Response({'users': UserSerializer(instance=anobj.members.all(), many=True).data}) return Response({'users': UserSerializer(instance=anobj.members.all(), many=True).data})
@detail_route(methods=['patch'])
def set_publish_mode(self, request, pk=None):
user = request.user
anobj = AnObj.objects.get(pk=pk)
new_publish_mode = int(request.DATA.get('publish_mode', 0))
if new_publish_mode not in xrange(3):
raise ValueError("Invalid publish mode")
# membership = get_object_or_404(AnObjMembership, anobj__id=pk, user=user)
try:
membership = AnObjMembership.objects.get(anobj=anobj, user=user)
except AnObjMembership.DoesNotExist:
if user == anobj.owner:
membership = AnObjMembership.objects.create(anobj=anobj, user=user, publish_mode=new_publish_mode)
else:
raise Http404()
if new_publish_mode == 2 and not (
anobj.allow_public_publishing or user == anobj.owner):
raise PermissionDenied
if membership.publish_mode != new_publish_mode:
membership.publish_mode = new_publish_mode
membership.save()
return Response({'publish_mode': membership.publish_mode})
class SharedAnObjViewSet(AnObjViewSet): class SharedAnObjViewSet(AnObjViewSet):
""" """
...@@ -114,26 +143,40 @@ class SharedAnObjViewSet(AnObjViewSet): ...@@ -114,26 +143,40 @@ class SharedAnObjViewSet(AnObjViewSet):
user = self.request.user user = self.request.user
q = Q(owner=user) q = Q(owner=user)
if self.action in ('list', 'retrieve'): if self.action in ('list', 'retrieve', 'members'):
q = q | Q(members=user) q = q | Q(members=user)
return AnObj.objects.filter(q) return AnObj.objects.filter(q)
@detail_route(methods=['patch']) @detail_route(methods=['get'])
def set_publish_mode(self, request, pk=None): def members(self, request, pk=None):
user = request.user anobj = self.get_object()
membership = get_object_or_404(AnObjMembership, anobj__id=pk, user=user) if anobj.allow_public_publishing:
users = UserSerializer(instance=anobj.members.all(), many=True).data
new_publish_mode = int(request.DATA.get('publish_mode', 0)) else:
if new_publish_mode not in xrange(3): users = []
raise ValueError("Invalid publish mode") return Response({'users': users})
#
if membership.publish_mode != new_publish_mode: # @detail_route(methods=['patch'])
membership.publish_mode = new_publish_mode # def set_publish_mode(self, request, pk=None):
membership.save() # user = request.user
# anobj = AnObj.objects.get(pk=pk)
return Response({'publish_mode': membership.publish_mode}) # membership = get_object_or_404(AnObjMembership, anobj__id=pk, user=user)
#
# new_publish_mode = int(request.DATA.get('publish_mode', 0))
# if new_publish_mode not in xrange(3):
# raise ValueError("Invalid publish mode")
#
# if new_publish_mode == 2 and not (
# anobj.allow_public_publishing or user == anobj.owner):
# raise PermissionDenied
#
# if membership.publish_mode != new_publish_mode:
# membership.publish_mode = new_publish_mode
# membership.save()
#
# return Response({'publish_mode': membership.publish_mode})
#
class UserViewSet(viewsets.ReadOnlyModelViewSet): class UserViewSet(viewsets.ReadOnlyModelViewSet):
""" """
...@@ -228,6 +271,3 @@ class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -228,6 +271,3 @@ class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet):
return Annotation.objects.filter(q).exclude(owner=self.request.user) return Annotation.objects.filter(q).exclude(owner=self.request.user)
#
\ No newline at end of file
...@@ -7,4 +7,5 @@ tmp ...@@ -7,4 +7,5 @@ tmp
**__OLD/* **__OLD/*
lib/* lib/*
!lib/whhg-font !lib/whhg-font
!lib/zip !lib/zip
\ No newline at end of file adim/playground.js
\ No newline at end of file
...@@ -52,6 +52,12 @@ function($){ ...@@ -52,6 +52,12 @@ function($){
}, },
userEngine: {
limit: 7,
remoteUrl: "http://path/to/suggestion/url/?q=%QUERY",
rateLimitWait: 300
},
// ==================================================================== // ====================================================================
// ----- Specific config // ----- Specific config
// The following settings are specific to the current use of the tool // The following settings are specific to the current use of the tool
......
...@@ -179,6 +179,25 @@ function ($, paper, Signal, Config, view) { ...@@ -179,6 +179,25 @@ function ($, paper, Signal, Config, view) {
}); });
}, },
loadSharedAnnotations: function(url) {
var d = $.Deferred();
$.ajax({
url: url,
dataType: "json",
success: function(annotations) {
d.resolve(annotations);
},
error: function(){
var err = {
msg: "Une erreur est survenue lors du chargement des données."
};
_events.loadingError.dispatch(err);
d.reject(err);
}
});
return d;
},
/** /**
* Save all annotations of the current user. Called manually. * Save all annotations of the current user. Called manually.
* Performed even if autoSave is false * Performed even if autoSave is false
......
...@@ -31,11 +31,12 @@ define([ ...@@ -31,11 +31,12 @@ define([
"adim/tools", "adim/tools",
"adim/attributes", "adim/attributes",
"adim/ui", "adim/ui",
"adim/users",
"helper/theme", "helper/theme",
"helper/fileUploader", "helper/fileUploader",
"helper/exporter" "helper/exporter"
], ],
function ($, paper, Signal, config, view, io, tools, attributes, ui, theme, fileUploader, exporter) { function ($, paper, Signal, config, view, io, tools, attributes, ui, Users, theme, fileUploader, exporter) {
var _events = {}; var _events = {};
var CANVAS_EL_ID = 'my-canvas'; var CANVAS_EL_ID = 'my-canvas';
...@@ -171,7 +172,7 @@ define([ ...@@ -171,7 +172,7 @@ define([
} }
if (config.mode === config.MODE_EDIT) { if (config.mode === config.MODE_EDIT) {
var userLayer = view.getOrCreateUserLayer(config.user.id, config.user.full_name); var userLayer = view.getOrCreateUserLayer(config.user.id, config.user.username);
userLayer.opacity = 1; userLayer.opacity = 1;
} }
...@@ -305,14 +306,18 @@ define([ ...@@ -305,14 +306,18 @@ define([
io.events.annotableLoaded.add(function(annotableData){ io.events.annotableLoaded.add(function(annotableData){
// Load the image // Load the image
// view.loadImage({url: annotableData.image_url, bgUrl: annotableData.image_bg_url}, // view.loadImage({url: annotableData.image_url, bgUrl: annotableData.image_bg_url},
Users.addUsers([{id: annotableData.owner, username: annotableData.owner_name}]);
//Users.addUsers(annotableData.members);
view.loadImage({url: config.annotable.image}, view.loadImage({url: config.annotable.image},
function(){ function(){
view.loadAnnotations(annotableData.annotations, config.user.id); view.loadAnnotations(annotableData.annotations, config.user.id);
} }
); );
}); });
}); });
var ADIM = { var ADIM = {
...@@ -332,6 +337,7 @@ define([ ...@@ -332,6 +337,7 @@ define([
config: config config: config
}; };
window[config.adim_global_varname] = ADIM; window[config.adim_global_varname] = ADIM;
window.DBG = Users;
if (config.ready && typeof config.ready === 'function') { if (config.ready && typeof config.ready === 'function') {
config.ready(ADIM); config.ready(ADIM);
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
define([ define([
"jquery", "jquery",
"underscore",
"signals", "signals",
"paper", "paper",
...@@ -30,6 +31,7 @@ define([ ...@@ -30,6 +31,7 @@ define([
"adim/io", "adim/io",
"adim/tools", "adim/tools",
"adim/attributes", "adim/attributes",
"adim/users",
"helper/exporter", "helper/exporter",
"anobj-mgr/views/AnObjMgr", "anobj-mgr/views/AnObjMgr",
...@@ -42,7 +44,7 @@ define([ ...@@ -42,7 +44,7 @@ define([
"bootstrap.spinedit", "bootstrap.spinedit",
"FileSaver" "FileSaver"
], ],
function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjMgr) { function($, _, Signal, paper, config, view, io, tools, attributes, Users, exporter, AnObjMgr) {
// ----- Locale variables ----------------------------- // ----- Locale variables -----------------------------
var _$w = $(window); var _$w = $(window);
var _canvas = null; var _canvas = null;
...@@ -117,7 +119,6 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM ...@@ -117,7 +119,6 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
view.events.imageLoaded.add(function(evt) { view.events.imageLoaded.add(function(evt) {
adjustCanvasToImage(evt.raster); adjustCanvasToImage(evt.raster);
$("#page-loader").remove(); $("#page-loader").remove();
}); });
view.events.annotationsLoaded.add(function() { view.events.annotationsLoaded.add(function() {
...@@ -568,6 +569,37 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM ...@@ -568,6 +569,37 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
// Reset tool to select, to avoid having an editing tool active in review mode // Reset tool to select, to avoid having an editing tool active in review mode
tools.activateTool('select'); tools.activateTool('select');
if (mode === 'review') {
if (navTabs.data('loaded')) {
view.toggleMembersLayers(true);
} else {
// Load Shared annotations
io.loadSharedAnnotations(config.api.annotables + config.annotable.id + '/shared/annotations/')
.done(function (annotations) {
navTabs.data('loaded', true);
//if (!config.user.guest) {
// // Owner anobjs, members already loaded
// view.loadAnnotations(annotations, null);
//} else {
// Guest view, load members' username for naming layers
$.ajax({
url: config.api.annotables + config.annotable.id + '/members/',
dataType: "json"
})
.done(function(data) {
Users.addUsers(data.users);
})
.always(function () {
view.loadAnnotations(annotations, null);
});
//}
});
}
} else if (mode === 'edit') {
view.toggleMembersLayers(false);
}
} }
/** /**
...@@ -875,12 +907,12 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM ...@@ -875,12 +907,12 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
var hiddenClass = "icon-eye-close"; var hiddenClass = "icon-eye-close";
var visibleClass = "icon-eye-open"; var visibleClass = "icon-eye-open";
var layerListItemHTML = [ var layerListItemTpl = _.template([
'<li class="list-group-item">', '<li class="list-group-item" data-layer-id="<%= id %>">',
'<span class="layer-status"></span>', '<span class="layer-status <%= visibleClass %>"></span>',
'<span class="layer-title"></span>', '<span class="layer-title"><%= name %></span>',
'</li>' '</li>'
].join(""); ].join(""));
function selectLayerListItem(item, exclusive) { function selectLayerListItem(item, exclusive) {
...@@ -963,12 +995,14 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM ...@@ -963,12 +995,14 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
function updateUserLayers() { function updateUserLayers() {
if (!config.ui.show_users_results) return; if (!config.ui.show_users_results) return;
$.each(view.getAllUsersLayers(), function(i,layer) { $.each(view.getAllUsersLayers(), function(i, layer) {
$(layerListItemHTML).prependTo(annotList) if (annotList.find("li[data-layer-id=" + layer.id + "]").length == 0)
.data("layerId", layer.id) $(layerListItemTpl({
.find(".layer-status").addClass(visibleClass).end() id:layer.id,
.find(".layer-title").text(layer.data.owner || layer.name) visibleClass:visibleClass,
; name: layer.data.owner || layer.name
}))
.prependTo(annotList);
}); });
} }
......
/**
* Copyright (C) 2014 Université de Lausanne, RISET,
* < http://www.unil.ch/riset/ >
* This file is part of AdIm.
* AdIm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* AdIm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* This copyright notice MUST APPEAR in all copies of the file.
*
* @AUTHOR: Julien Furrer <Julien.Furrer@unil.ch>
* @CREATION-DATE: 18.11.14
*
*/
define([
"jquery",
"underscore",
"bloodhound",
"adim/config"
],
function($, _, Bloodhound, config){
var engine = null;
function initialize() {
engine = new Bloodhound({
datumTokenizer: function(datum) {