Gitlab CSE Unil

Commit 0da00dee authored by Julien Furrer's avatar Julien Furrer
Browse files

Added support for multiple owners of an AnObj

Note: there is no user interface for editing the owners list.
It is supposed to be done through the API (to be done)
parent c3ac5f4f
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('adim', '0002_auto_20150417_1302'),
]
operations = [
migrations.AddField(
model_name='anobj',
name='owners',
field=models.ManyToManyField(related_name='owned_anobjs', verbose_name='owners', to=settings.AUTH_USER_MODEL, blank=True),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def init_owners(apps, schema_editor):
"""
Initialize new `owners` field by adding the current `owner` to it
:param apps:
:param schema_editor:
:return:
"""
AnObj = apps.get_model("adim", "AnObj")
for anobj in AnObj.objects.all():
if len(anobj.owners.all()) == 0:
anobj.owners.add(anobj.owner)
class Migration(migrations.Migration):
dependencies = [
('adim', '0003_anobj_owners'),
]
operations = [
migrations.RunPython(init_owners),
]
......@@ -16,6 +16,8 @@ from jsonfield import JSONField
from eav.models import BaseEntity, BaseSchema, BaseAttribute, BaseChoice
from adim_project.utils.decorators import cache
__all__ = ('AOType', 'AOSchema', 'AOChoice', 'AOAttribute', 'AnObj', 'AnObjMembership')
# code from from uuid._random_getnode()
......@@ -93,6 +95,7 @@ class AnObj(models.Model):
name = models.CharField(max_length=125)
owner = models.ForeignKey(User, verbose_name=_("owner"), related_name='anobjs')
owners = models.ManyToManyField(User, verbose_name=_("owners"), related_name='owned_anobjs', blank=True)
locked = models.BooleanField(verbose_name=_("locked"), default=False)
sharing_mode = models.IntegerField(verbose_name=_("sharing mode"), default=0, blank=True)
......@@ -117,21 +120,35 @@ class AnObj(models.Model):
ordering = ["-id"]
def __unicode__(self):
return u"{}".format(
self.name
)
return u"{}".format(self.name)
def save(self, *args, **kwargs):
"""
Override save method to add actions on creation:
caluclate the `uuid`
append to `owner` to the `owners` list
:param args:
:param kwargs:
:return:
"""
new_anobj = False
# set the uuid upon object creation
if not self.uuid:
self.set_uuid(no_save=True)
new_anobj = True
super(AnObj, self).save(*args, **kwargs)
# if not self._thumb_url:
# self._thumb_url = self._create_thumbnail()
# self.save()
if new_anobj:
if len(self.owners.all()) == 0:
self.owners.add(self.owner)
def set_uuid(self, no_save=False):
"""
Calculate and set the UUID, only if not already set
:param no_save:
:return:
"""
if not self.uuid:
# Using the random node initialized at module level
# to avoid compromising network address
......@@ -158,6 +175,15 @@ class AnObj(models.Model):
"""
AnObjMembership.objects.filter(anobj=self, user__in=args).delete()
@cache(seconds=10)
def is_owned(self, user_id):
"""
Return True if the User if an owner of the anobj
This is used as function, so the result can be cached
"""
return user_id in self.owners.all().values_list('id', flat=True)
# def _create_thumbnail(self):
# if not self.image:
# return
......
......@@ -53,7 +53,9 @@ class PermissionClass(object):
raise PermissionDenied()
# Owner can always access
if anobj.owner == user:
# if anobj.owner == user:
# if user in anobj.owners.all():
if anobj.is_owned(user.id):
return True
members = anobj.members.all()
......@@ -178,7 +180,9 @@ def check_anobj_permission(request, anobj):
p = get_permission_class(anobj.sharing_mode)
if p is not None:
p.check_permission(request, anobj)
elif request.user != anobj.owner:
# elif request.user != anobj.owner:
# elif request.user not in anobj.owners.all():
elif not anobj.is_owned(request.user.id):
raise PermissionDenied()
......@@ -192,4 +196,6 @@ def has_anobj_access(request, anobj):
:param anobj:
:return: boolean
"""
return request.user == anobj.owner or request.user in anobj.members.all()
\ No newline at end of file
# return request.user == anobj.owner or request.user in anobj.members.all()
# return request.user in anobj.owners.all() or request.user in anobj.members.all()
return anobj.is_owned(request.user.id) or request.user in anobj.members.all()
\ No newline at end of file
......@@ -43,7 +43,8 @@ class AnnotationSerializer(serializers.ModelSerializer):
class BaseAnObjSerializer(serializers.ModelSerializer):
owner_name = serializers.ReadOnlyField(source='owner.username')
#owner_name = serializers.ReadOnlyField(source='owner.username')
owner_name = serializers.SerializerMethodField()
annotations = serializers.SerializerMethodField()
# members = serializers.SerializerMethodField('get_members')
# members = UserSerializer(many=True, read_only=True)
......@@ -57,7 +58,7 @@ class BaseAnObjSerializer(serializers.ModelSerializer):
class Meta:
model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'annotations', 'sharing_mode', 'locked')
fields = ('id', 'uuid', 'name', 'owners', 'owner', 'owner_name', 'annotations', 'sharing_mode', 'locked')
read_only_fields = ('members',)
def get_annotations(self, anobj):
......@@ -66,6 +67,9 @@ class BaseAnObjSerializer(serializers.ModelSerializer):
context=self.context)
return annot_serializer.data
def get_owner_name(self, anobj):
return anobj.owners.all()[0].username
# def get_sharing_opts(self, anobj):
# request = self.context.get('request')
# if request is not None and request.user == anobj.owner:
......@@ -104,7 +108,7 @@ class AnObjSerializer(BaseAnObjSerializer):
class Meta:
model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'annotations', 'members',
fields = ('id', 'uuid', 'name', 'owners', 'owner', 'owner_name', 'annotations', 'members',
'sharing_mode', 'sharing_opts', 'locked', 'allow_public_publishing')
......@@ -112,11 +116,11 @@ class SharedAnObjSerializer(BaseAnObjSerializer):
class Meta:
model = AnObj
fields = ('id', 'uuid', 'name', 'owner', 'owner_name', 'annotations', 'locked',
fields = ('id', 'uuid', 'name', 'owners', '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')
\ No newline at end of file
fields = ('id', 'uuid', 'name', 'owners', 'owner', 'locked', 'sharing_mode')
\ No newline at end of file
......@@ -45,7 +45,8 @@ class AnObjViewSet(viewsets.ModelViewSet):
return also shared anobjs to guests for read actions
"""
user = self.request.user
q = Q(owner=user)
# q = Q(owner=user)
q = Q(owners=user)
if self.action == 'list':
q = q | Q(members=user)
......@@ -56,7 +57,7 @@ class AnObjViewSet(viewsets.ModelViewSet):
anobj = self.get_object()
if request.method != 'GET':
user_model = get_model(*settings.AUTH_USER_MODEL.split('.'))
user_model = get_user_model()
members = []
for member_data in request.DATA.get('members', []):
try:
......@@ -100,13 +101,16 @@ class AnObjViewSet(viewsets.ModelViewSet):
try:
membership = AnObjMembership.objects.get(anobj=anobj, user=user)
except AnObjMembership.DoesNotExist:
if user == anobj.owner:
# if user == anobj.owner:
# if user in anobj.owners.all():
if anobj.is_owned(user.id):
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):
anobj.allow_public_publishing or anobj.is_owned(user.id)):
# anobj.allow_public_publishing or user in anobj.owners.all()):
raise PermissionDenied
if membership.publish_mode != new_publish_mode:
......@@ -141,11 +145,12 @@ class SharedAnObjViewSet(AnObjViewSet):
# return AnObj.objects.filter(members=self.request.user)
user = self.request.user
q = Q(owner=user)
# q = Q(owner=user)
q = Q(owners=user)
if self.action in ('list', 'retrieve', 'members'):
q = q | Q(members=user)
return AnObj.objects.filter(q)
return AnObj.objects.filter(q).distinct()
@detail_route(methods=['get'])
def members(self, request, pk=None):
......@@ -177,6 +182,7 @@ class SharedAnObjViewSet(AnObjViewSet):
# return Response({'publish_mode': membership.publish_mode})
#
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
List of registered users for an AnObj.
......@@ -260,7 +266,8 @@ class SharedAnnotationViewSet(viewsets.ReadOnlyModelViewSet):
anots = anobj.annotations.filter(owner__anobjmembership__publish_mode__gte=<publish_level>, owner__anobjmembership__anobj=anobj).select_related('owner')
"""
anobj = get_object_or_404(AnObj, pk=self.kwargs.get('anobjs_pk', -1))
publish_level = 1 if self.request.user == anobj.owner else 2
# publish_level = 1 if self.request.user in anobj.owners.all() else 2
publish_level = 1 if anobj.is_owned(self.request.user.id) else 2
q = (
Q(annotable=anobj) &
......
......@@ -98,7 +98,7 @@ define([
// config: config
//};
//
//// window[config.adim_global_varname] = ADIM; // <-- FIXME: should be removed, it could be a security issue
//// window[config.adim_global_varname] = ADIM;
//
//if (config.ready && typeof config.ready === 'function') {
// config.ready(ADIM);
......
......@@ -989,10 +989,11 @@ function($, _, Signal, paper, config, view, io, tools, attributes, Users, export
if (!config.ui.show_users_results) return;
$.each(view.getAllUsersLayers(), function(i, layer) {
console.log( layer.data );
if (annotList.find("li[data-layer-id=" + layer.id + "]").length == 0)
$(layerListItemTpl({
id:layer.id,
visibleClass:visibleClass,
id: layer.id,
visibleClass: visibleClass,
name: layer.data.owner || layer.name
}))
.prependTo(annotList);
......
......@@ -28,7 +28,9 @@ define([
function(_, Backbone, Config){
return Backbone.Model.extend({
initialize: function(){
this.owned = Config && Config.user ? (this.get('owner') === Config.user.id) : false;
//this.owned = Config && Config.user ? (this.get('owner') === Config.user.id) : false;
this.owned = Config && Config.users ? (this.get('owners').indexOf(Config.user.id) > -1) : false;
this.set('owned', this.owned);
},
url: function() {
......
......@@ -81,7 +81,7 @@ function($, _, Backbone, Config, AnObjCollection, AnObjListItemView, AnObjProper
var view = new AnObjListItemView({
model: anobj
});
var targetCtnr = (anobj.get('owner') === Config.user.id) ? this.anobjListCtnr : this.sharedAnobjListCtnr;
var targetCtnr = anobj.get('owned') ? this.anobjListCtnr : this.sharedAnobjListCtnr;
targetCtnr.append(view.render().el);
// this.listenTo(view, "editName", this.editAnobjName);
},
......
......@@ -137,7 +137,6 @@ define([
.toggleClass("aom-prop-unlocked", !locked);
var sharingModeSelect = this.sharingPropEl.find("[name=aom-prop-shared]");
console.log("sharingMode", sharingMode);
if (_.isUndefined(sharingMode) || sharingMode === "" ) {
sharingModeSelect.attr("disabled", "disabled");
} else {
......@@ -185,10 +184,11 @@ define([
}
// see http://underscorejs.org/#chain
var anobjOwners = model.get('owners');
$el.html(_.chain(members)
// Filter out owner from members list
// Filter out owners of the anobj from the members list
// (needed because of the AnObjMembership used to store the publishing status of the owner)
.filter(function(m){ return m.id !== config.user.id; })
.filter(function(m){ return anobjOwners.indexOf(m.id) === -1; })
// Ordering by username
.sortBy('username')
// Apply template
......
......@@ -3,9 +3,8 @@
{% block navbar_content %}
<ul class="nav navbar-nav">
<li><a href="#" class="adim-image-name {% if anobj.owner.id != user.id %}read-only{% endif %}">
<li><a href="#" class="adim-image-name {{ is_owner|yesno:",read-only" }}">
<span class="text">{{ anobj.name }}</span>
{# &nbsp;<span class="glyphicon glyphicon-pencil" style="font-size: 12px;"></span>#}
</a></li>
</ul>
{{ block.super }}
......@@ -45,7 +44,7 @@ window.ADIM_CONFIG = {
api: {
baseUrl: "{% url "api-root" %}",
annotables: "{% if anobj.owner == user %}{% url "anobjs-list" %}{% else %}{% url "shared-anobjs-list" %}{% endif %}",
annotables: "{% if is_owner %}{% url "anobjs-list" %}{% else %}{% url "shared-anobjs-list" %}{% endif %}",
annotations: "{% url "annotations-list" %}"
},
......@@ -70,7 +69,7 @@ window.ADIM_CONFIG = {
{% if membership %}
,membership: { publish_mode: {{ membership.publish_mode }} }
{% endif %}
,guest: {% if anobj.owner.id != user.id %}true{% else %}false{% endif %}
,guest: {{ is_owner|yesno:"false,true" }}
,shibboleth: true
},
......
......@@ -120,34 +120,28 @@
<label>
<input name="publish-mode" value="0" type="radio"/>
<span class="icon-home"></span>
{# <span class="lead"><span class="label label-info">#}
<strong>privé</strong>
{# </span></span>#}
<span class="help-block">Les annotations sont accessibles pour vous seulement.</span>
</label>
</div>
{% if anobj.owner != user %}
<div class="radio">
<label>
<input name="publish-mode" value="1" type="radio"/>
<span class="icon-user"></span>
{# <span class="lead"><span class="label label-warning">#}
<strong>propriétaire</strong>
{# </span></span>#}
<span class="help-block">Les annotations peuvent être vues par le/la propriétaire de l'image uniquement.
{% if anobj %}Pour cette image il s'agit de: <strong>«{{ anobj.owner }}»</strong> {% endif %}
</span>
</label>
</div>
{% endif %}
{% if anobj.allow_public_publishing or anobj.owner == user %}
{% if anobj.allow_public_publishing or is_owner %}
<div class="radio">
<label>
<input name="publish-mode" value="2" type="radio"/>
<span class="icon-groups-friends"></span><strong>public</strong>
<span class="help-block">
Les annotations peuvent être vues par toutes les personnes qui ont un accès à l'image.
{% if anobj.owner != user %}Pour connaitre précisément la liste de ces personnes,
{% if not is_owner %}Pour connaitre précisément la liste de ces personnes,
addressez vous à la/le propriétaire de l'image.{% endif %}
</span>
</label>
......@@ -224,7 +218,7 @@
<% if (locked) { %><span class="glyphicon glyphicon-lock" title="Vérouillé"></span><% } %>
</div>
<% if (owner == {{ user.id }}) { %><button class="close" title="Supprimer l'image">
<% if (owned) { %><button class="close" title="Supprimer l'image">
<span class="glyphicon glyphicon-remove-circle"></span>
</button><% } %>
<a href="{{ annotate_url }}<%= uuid %>/" title="Editer l'image">
......
......@@ -149,7 +149,9 @@ def annotate(request, anobj_uuid=None):
if len(anobj_uuid) < 32:
return HttpResponseRedirect(reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj.uuid}))
is_owner = request.user == anobj.owner
# is_owner = request.user == anobj.owner
# is_owner = request.user in anobj.owners.all()
is_owner = anobj.is_owned(request.user.id)
context.update({'is_owner': is_owner})
# Detailed check for permissions
......@@ -191,20 +193,21 @@ def annotate(request, anobj_uuid=None):
# Determine if we may display shared annotations
if is_owner:
ownerMembership = membership
owner_membership = membership
else:
try:
ownerMembership = AnObjMembership.objects.get(anobj=anobj, user=anobj.owner)
owner_membership = AnObjMembership.objects.get(anobj=anobj, user=anobj.owner)
except AnObjMembership.DoesNotExist:
ownerMembership = None
owner_membership = None
context.update({'display_shared_annotations':
(anobj.sharing_mode != SHARING_MODE_NONE) and
(
(request.user == anobj.owner) or
# (request.user == anobj.owner) or
anobj.is_owned(request.user.id) or
anobj.allow_public_publishing or
(
ownerMembership and ownerMembership.publish_mode == 2
owner_membership and owner_membership.publish_mode == 2
)
)
})
......
__author__ = 'jfurrer'
# *-* encoding: utf-8 *-*
from hashlib import sha1
from django.core.cache import cache as _djcache
def cache(seconds=300):
"""
Cache the result of a function call for the specified number of seconds,
using Django's caching mechanism.
Assumes that the function never returns None (as the cache returns None to indicate a miss),
and that the function's result only depends on its parameters.
Note that the ordering of parameters is important. e.g. myFunction(x = 1, y = 2), myFunction(y = 2, x = 1),
and myFunction(1,2) will each be cached separately.
Usage:
@cache(600)
def myExpensiveMethod(parm1, parm2, parm3):
....
return expensiveResult
"""
# from https://djangosnippets.org/snippets/564/raw/
def do_cache(f):
def x(*args, **kwargs):
key = sha1(str(f.__module__) + str(f.__name__) + str(args) + str(kwargs)).hexdigest()
result = _djcache.get(key)
if result is None:
result = f(*args, **kwargs)
_djcache.set(key, result, seconds)
return result
return x
return do_cache
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment