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
adim_project/wsgi.py
adim_project/settings/local_settings.py
adim_project/apache.conf.tpl
\ No newline at end of file
adim_project/apache.conf.tpl
adim_app/playground.py
adim_app/templates/playground
adim_app/static/js/playground.js
\ No newline at end of file
# coding=utf-8
from __future__ import unicode_literals
import os
import user
import uuid, random
from django.db import models
from django.conf import settings
......@@ -124,6 +125,7 @@ class AnObj(BaseEntity):
self._set_uuid(no_save=True)
super(AnObj, self).save(*args, **kwargs)
# if not self._thumb_url:
# self._thumb_url = self._create_thumbnail()
# self.save()
......@@ -143,9 +145,11 @@ class AnObj(BaseEntity):
publish_mode = kwargs.get('publish_mode', 0)
memberships = []
for user in args:
memberships.append(AnObjMembership(anobj=self, user=user, publish_mode=publish_mode))
if memberships:
AnObjMembership.objects.bulk_create(memberships)
AnObjMembership.objects.get_or_create(anobj=self, user=user, defaults={'publish_mode': publish_mode})
#
# memberships.append(AnObjMembership(anobj=self, user=user, publish_mode=publish_mode))
# if memberships:
# AnObjMembership.objects.bulk_create(memberships)
def remove_members(self, *args):
"""
......
......@@ -103,7 +103,7 @@ class AnObjSerializer(BaseAnObjSerializer):
class Meta:
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')
......@@ -111,11 +111,11 @@ class SharedAnObjSerializer(BaseAnObjSerializer):
class Meta:
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')
class AnObjListSerializer(BaseAnObjSerializer):
class Meta:
model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'locked', 'sharing_mode', 'ao_type')
\ No newline at end of file
fields = ('id', 'uuid', 'name', 'owner', 'locked', 'sharing_mode')
\ No newline at end of file
......@@ -2,6 +2,7 @@
from __future__ import unicode_literals
import json
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.http.response import HttpResponseForbidden, Http404
from django.conf import settings
from django.db.models import get_model, Q
......@@ -13,7 +14,7 @@ from adim.serializers import AnObjSerializer, SharedAnObjSerializer, AnObjListSe
from adim.permissions import has_anobj_access
from rest_framework import generics, viewsets
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.parsers import FileUploadParser
......@@ -49,44 +50,72 @@ class AnObjViewSet(viewsets.ModelViewSet):
if self.action == 'list':
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):
anobj = self.get_object()
user_model = get_model(*settings.AUTH_USER_MODEL.split('.'))
members = []
for member_data in request.DATA.get('members', []):
try:
q = Q(pk=-1)
if member_data.get('id'):
q = Q(pk=member_data.get('id'))
elif member_data.get('username'):
q = Q(username=member_data.get('username'))
members.append(user_model.objects.get(q))
except user_model.DoesNotExist:
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'))
elif request.method == 'DELETE':
opts_members.remove(member_data.get('username'))
anobj.sharing_opts['members'] = opts_members
anobj.save()
if request.method in ('PATCH', 'POST'):
# anobj.members.add(*members)
anobj.add_members(*members)
elif request.method == 'DELETE':
# anobj.members.remove(*members)
anobj.remove_members(*members)
if request.method != 'GET':
user_model = get_model(*settings.AUTH_USER_MODEL.split('.'))
members = []
for member_data in request.DATA.get('members', []):
try:
q = Q(pk=-1)
if member_data.get('id'):
q = Q(pk=member_data.get('id'))
elif member_data.get('username'):
q = Q(username=member_data.get('username'))
members.append(user_model.objects.get(q))
except user_model.DoesNotExist:
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'))
elif request.method == 'DELETE':
opts_members.remove(member_data.get('username'))
anobj.sharing_opts['members'] = opts_members
anobj.save()
if request.method in ('PATCH', 'POST'):
# anobj.members.add(*members)
anobj.add_members(*members)
elif request.method == 'DELETE':
# anobj.members.remove(*members)
anobj.remove_members(*members)
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):
"""
......@@ -114,26 +143,40 @@ class SharedAnObjViewSet(AnObjViewSet):
user = self.request.user
q = Q(owner=user)
if self.action in ('list', 'retrieve'):
if self.action in ('list', 'retrieve', 'members'):
q = q | Q(members=user)
return AnObj.objects.filter(q)
@detail_route(methods=['patch'])
def set_publish_mode(self, request, pk=None):
user = request.user
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 membership.publish_mode != new_publish_mode:
membership.publish_mode = new_publish_mode
membership.save()
return Response({'publish_mode': membership.publish_mode})
@detail_route(methods=['get'])
def members(self, request, pk=None):
anobj = self.get_object()
if anobj.allow_public_publishing:
users = UserSerializer(instance=anobj.members.all(), many=True).data
else:
users = []
return Response({'users': users})
#
# @detail_route(methods=['patch'])
# def set_publish_mode(self, request, pk=None):
# user = request.user
# anobj = AnObj.objects.get(pk=pk)
# 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):
"""
......@@ -228,6 +271,3 @@ class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet):
return Annotation.objects.filter(q).exclude(owner=self.request.user)
#
\ No newline at end of file
......@@ -7,4 +7,5 @@ tmp
**__OLD/*
lib/*
!lib/whhg-font
!lib/zip
\ No newline at end of file
!lib/zip
adim/playground.js
\ No newline at end of file
......@@ -52,6 +52,12 @@ function($){
},
userEngine: {
limit: 7,
remoteUrl: "http://path/to/suggestion/url/?q=%QUERY",
rateLimitWait: 300
},
// ====================================================================
// ----- Specific config
// The following settings are specific to the current use of the tool
......
......@@ -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.
* Performed even if autoSave is false
......
......@@ -31,11 +31,12 @@ define([
"adim/tools",
"adim/attributes",
"adim/ui",
"adim/users",
"helper/theme",
"helper/fileUploader",
"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 CANVAS_EL_ID = 'my-canvas';
......@@ -171,7 +172,7 @@ define([
}
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;
}
......@@ -305,14 +306,18 @@ define([
io.events.annotableLoaded.add(function(annotableData){
// Load the image
// 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},
function(){
view.loadAnnotations(annotableData.annotations, config.user.id);
}
);
});
});
var ADIM = {
......@@ -332,6 +337,7 @@ define([
config: config
};
window[config.adim_global_varname] = ADIM;
window.DBG = Users;
if (config.ready && typeof config.ready === 'function') {
config.ready(ADIM);
......
......@@ -21,6 +21,7 @@
define([
"jquery",
"underscore",
"signals",
"paper",
......@@ -30,6 +31,7 @@ define([
"adim/io",
"adim/tools",
"adim/attributes",
"adim/users",
"helper/exporter",
"anobj-mgr/views/AnObjMgr",
......@@ -42,7 +44,7 @@ define([
"bootstrap.spinedit",
"FileSaver"
],
function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjMgr) {
function($, _, Signal, paper, config, view, io, tools, attributes, Users, exporter, AnObjMgr) {
// ----- Locale variables -----------------------------
var _$w = $(window);
var _canvas = null;
......@@ -117,7 +119,6 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
view.events.imageLoaded.add(function(evt) {
adjustCanvasToImage(evt.raster);
$("#page-loader").remove();
});
view.events.annotationsLoaded.add(function() {
......@@ -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
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
var hiddenClass = "icon-eye-close";
var visibleClass = "icon-eye-open";
var layerListItemHTML = [
'<li class="list-group-item">',
'<span class="layer-status"></span>',
'<span class="layer-title"></span>',
'</li>'
].join("");
var layerListItemTpl = _.template([
'<li class="list-group-item" data-layer-id="<%= id %>">',
'<span class="layer-status <%= visibleClass %>"></span>',
'<span class="layer-title"><%= name %></span>',
'</li>'
].join(""));
function selectLayerListItem(item, exclusive) {
......@@ -963,12 +995,14 @@ function($, Signal, paper, config, view, io, tools, attributes, exporter, AnObjM
function updateUserLayers() {
if (!config.ui.show_users_results) return;
$.each(view.getAllUsersLayers(), function(i,layer) {
$(layerListItemHTML).prependTo(annotList)
.data("layerId", layer.id)
.find(".layer-status").addClass(visibleClass).end()
.find(".layer-title").text(layer.data.owner || layer.name)
;
$.each(view.getAllUsersLayers(), function(i, layer) {
if (annotList.find("li[data-layer-id=" + layer.id + "]").length == 0)
$(layerListItemTpl({
id:layer.id,
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) {
return Bloodhound.tokenizers.nonword(datum.username)
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: config.userEngine.limit,
remote: {
url: config.userEngine.remoteUrl,
rateLimitWait: config.userEngine.rateLimitWait
}
});
engine.initialize();
}
function addUsers(users) {
if ($.isArray(users) && users.length >= 0)
engine.add(users);
}
function getById(id) {
var user = _.find(engine.index.datums, function(datum) {
return datum.id == id;
});
return user ? user.username : "";
}
function searchById(id) {
var d = $.Deferred(),
user = _.find(engine.index.datums, function(datum) {
return datum.id == id;
});
if (user) {
d.resolve(user.username);
} else {
d.reject();
}
return d;
}
function loadById(ids) {}
initialize();
return {
engine: engine,
addUsers: addUsers,
getById: getById,
searchById: searchById
};
});
\ No newline at end of file
......@@ -31,9 +31,10 @@ define(
// ----- app
"adim/config",
"adim/users",
"adim/tools"
],
function ($, paper, Signal, config, tools) {
function ($, paper, Signal, config, Users, tools) {
// ----- Locale variables -----------------------------
var _canvas = null;
......@@ -100,7 +101,8 @@ function ($, paper, Signal, config, tools) {
function _getOrCreateUserLayer(user, userFullName) {
var layerName = "user_" + user;
var ownerName = userFullName || _users[user] || user;
//var ownerName = userFullName || _users[user] || user;
var ownerName = userFullName || Users.getById(user) || user;
return _getOrCreateLayerByName(layerName, {owner: ownerName});
}
......@@ -567,7 +569,7 @@ function ($, paper, Signal, config, tools) {
loadAnnotations: function(annotations, currentUserId) {
for (var a=annotations.length-1, annotation; annotation = annotations[a]; a--) {
var isOwnAnnotation = annotation.owner === currentUserId;
if (config.mode === config.MODE_REVIEW || isOwnAnnotation)
if (config.ui.show_users_results || isOwnAnnotation)
_loadAnnotation(annotation, isOwnAnnotation);
}
if (currentUserId) {
......@@ -669,8 +671,19 @@ function ($, paper, Signal, config, tools) {
},
toggleLayer: function (layerName, show) {
return _toggleLayer(layerName, show);
}
},
toggleMembersLayers: function(show) {
var ownerLayer = _getLayerByName('user_' +
((config && config.user && config.user.id) || "___")
);
paper.project.deselectAll();
tools.events.annotationSelected.dispatch(null);
$.each(this.getAllUsersLayers(), function(i, l){
_toggleLayer(l.name, (l.name === ownerLayer.name) ? true : show);
});
ownerLayer.activate();
}
, _raster: function(){ return _raster; }
};
......
......@@ -24,9 +24,12 @@ define([
"backbone",
"anobj-mgr/config",
"zeroclipboard"
"adim/users",
"zeroclipboard",
"jquery.typeahead"
],
function($, _, Backbone, config, ZeroClipboard) {
function($, _, Backbone, config, Users, ZeroClipboard) {
var SHARING_MODE_NONE = 0,
SHARING_MODE_MANUAL