Commit 9ed9f047 authored by gijs's avatar gijs

Merge branch 'master' of gitlab.constantvzw.org:osp/work.w

parents 29c6b6ff 42fa45ba
......@@ -133,7 +133,6 @@ STATICFILES_FINDERS = (
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
)
......
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from adminsortable2.admin import SortableAdminMixin, SortableInlineAdminMixin
from .models import Attachment, Score, FeaturedScore
......@@ -13,8 +12,10 @@ class AttachmentAdmin(admin.ModelAdmin):
pass
class ScoreAdmin(GuardedModelAdmin):
pass
class ScoreAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# obj.user = request.user
super(ScoreAdmin, self).save_model(request, obj, form, change)
admin.site.register(FeaturedScore, FeaturedScoreAdmin)
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2019-01-04 14:25
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('playground', '0022_auto_20181217_1010'),
]
operations = [
migrations.AddField(
model_name='score',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='creator', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='score',
name='is_public',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='score',
name='shared_with',
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2019-01-04 14:26
from __future__ import unicode_literals
from django.db import migrations
def populate_permissions(apps, schema_editor):
from guardian.shortcuts import get_users_with_perms
from guardian.utils import get_anonymous_user
# We can't import the models directly as it may be a newer
# version than this migration expects. We use the historical version.
Score = apps.get_model('playground', 'Score')
User = apps.get_model('auth', 'User')
creator = User.objects.get(username="jeanne")
anonymous_user = get_anonymous_user()
for score in Score.objects.all():
score.created_by = creator
users = get_users_with_perms(score)
score.is_public = users.filter(id=anonymous_user.id).exists()
users = users.exclude(id__in=[creator.id, anonymous_user.id])
for user in users:
score.shared_with.add(User.objects.get(id=user.id))
score.save()
class Migration(migrations.Migration):
dependencies = [
('playground', '0023_auto_20190104_1425'),
]
operations = [
migrations.RunPython(populate_permissions),
]
from django.db import models
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import JSONField
from taggit.managers import TaggableManager
class Attachment(models.Model):
title = models.CharField(max_length=255)
attachment = models.FileField(blank=True, null=True)
......@@ -34,9 +37,17 @@ class Score(models.Model):
mainline = JSONField(blank=True)
language = models.TextField(blank=True)
is_public = models.BooleanField(default=False)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name="creator")
shared_with = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
def __str__(self):
return self.title
# def save(self, *args, **kwargs):
# import ipdb; ipdb.set_trace()
# super(Score, self).save(*args, **kwargs) # Call the "real" save() method.
def get_absolute_url(self):
return "/partitions/{}".format(self.id)
......
......@@ -4,10 +4,12 @@ from rest_framework import serializers
from rest_framework_recursive.fields import RecursiveField
from taggit_serializer.serializers import (TagListSerializerField,
TaggitSerializer)
from guardian.shortcuts import assign_perm, get_users_with_perms, get_user_perms, remove_perm
### from guardian.shortcuts import assign_perm, get_users_with_perms, get_user_perms, remove_perm
from django.contrib.auth.models import User
from django.contrib.flatpages.models import FlatPage
from django.contrib.auth.validators import UnicodeUsernameValidator
class FlatPageSerializer(serializers.HyperlinkedModelSerializer):
......@@ -24,6 +26,13 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['username', 'id']
# Dealing with "A user with that username already exists." Thanks!
# https://medium.com/django-rest-framework/dealing-with-unique-constraints-in-nested-serializers-dade33b831d9
extra_kwargs = {
'username': {
'validators': [UnicodeUsernameValidator()],
}
}
class AttachmentSerializer(serializers.HyperlinkedModelSerializer):
......@@ -35,55 +44,40 @@ class AttachmentSerializer(serializers.HyperlinkedModelSerializer):
# read_only_fields = ('attachment',)
class PermissionListSerializer(serializers.ListSerializer):
def to_representation(self, obj):
perms = get_users_with_perms(obj, attach_perms=True)
return [{"username": k.username, "permissions": v} for k, v in perms.items()]
def to_internal_value(self, data):
print(data)
# return data
# I don't understand why I can't just return data
return {"permissions": data}
class PermissionSerializer(serializers.Serializer):
class Meta:
list_serializer_class = PermissionListSerializer
class ScoreSerializer(TaggitSerializer, serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
tags = TagListSerializerField(required=False)
permissions = PermissionSerializer(source="*", many=True)
is_editable = serializers.SerializerMethodField()
created_by = UserSerializer(required=False)
shared_with = UserSerializer(many=True, required=False)
class Meta:
model = Score
fields = '__all__'
def create(self, validated_data):
validated_data.pop('permissions', None)
validated_data["created_by"] = self.context['request'].user
instance = Score.objects.create(**validated_data)
assign_perm("view_score", self.context['request'].user, instance)
return instance
def get_is_editable(self, obj):
return self.context['request'].user.has_perm('change_score', obj)
current_user = self.context['request'].user
return obj.created_by == current_user or current_user in obj.shared_with.all()
def update(self, instance, validated_data):
# Dealing with "A user with that username already exists." Thanks!
# https://medium.com/django-rest-framework/dealing-with-unique-constraints-in-nested-serializers-dade33b831d9
created_by_data = validated_data.pop('created_by')
shared_with = validated_data.pop('shared_with')
self.instance.shared_with.clear()
for i in shared_with:
user = User.objects.get(username=i["username"])
if user:
print("adding {}".format(user.username))
self.instance.shared_with.add(user)
instance = super(ScoreSerializer, self).update(instance, validated_data)
for user, perms in get_users_with_perms(instance, attach_perms=True).items():
for perm in perms:
remove_perm(perm, user, instance)
for i in instance.permissions:
user = User.objects.get(username=i.get('username'))
perms = i.get('permissions')
for perm in perms:
assign_perm(perm, user, instance)
return instance
......
This diff is collapsed.
......@@ -28,6 +28,16 @@ window.W = window.W || {};
},
credits: function () {
document.title = 'Organon: Crédits';
var baseView = this.getOption('application').getView();
var view = new W.CreditsView();
baseView.showChildView('main', view);
baseView.getChildView('help').triggerMethod("hide");
},
userList: function () {
document.title = 'Organon: liste des utilisateurs';
......
......@@ -6,7 +6,6 @@ window.W = window.W || {};
(function(undefined) {
'use strict';
Marionette.TemplateCache.prototype.compileTemplate = function compileTemplate(rawTemplate, options) {
return W.extendedTemplate(rawTemplate, options);
}
......@@ -31,7 +30,6 @@ window.W = window.W || {};
W.config = W.config || {};
W.config.lang = W.utils.getUserLanguage();
var scoreApp = new W.ScoreApp();
scoreApp.start();
})();
......@@ -55,7 +55,7 @@ window.W = window.W || {};
W.ChoiceModel = Backbone.Model.extend({});
W.UserModel = Backbone.Model.extend({
W.UserModel = Backbone.RelationalModel.extend({
urlRoot: '/api/users/',
label: function () {
......@@ -74,9 +74,11 @@ window.W = window.W || {};
W.UserAuthModel = Backbone.Model.extend({
urlRoot: '/rest-auth/user/',
defaults: {
username: ""
},
idAttribute: "pk",
// defaults: {
// username: ""
// },
url: function () {
var original_url = Backbone.Model.prototype.url.call(this);
......@@ -84,6 +86,10 @@ window.W = window.W || {};
return parsed_url;
},
isLoggedIn: function () {
return this.model.id ? true : false
}
});
......@@ -123,7 +129,8 @@ window.W = window.W || {};
presentation: "",
effectif: "",
language: "",
permissions: {}
permissions: {},
shared_with: []
},
urlRoot: '/api/scores/',
......@@ -155,6 +162,17 @@ window.W = window.W || {};
includeInJSON: false,
type: Backbone.HasOne
}
},
{
type: Backbone.HasMany,
key: 'shared_with',
relatedModel: 'UserModel',
collectionType: 'UserCollection',
reverseRelation: {
key: 'score',
includeInJSON: false,
type: Backbone.HasOne
}
}
],
......@@ -294,6 +312,10 @@ window.W = window.W || {};
return _.reduce(this.get('sublines').models, function (depth, line) { return Math.max(line.getDepth(), depth); }, 0) + 1;
},
getChildCount: function () {
return this.get('sublines').length;
},
addSubLine: function () {
var sublines = this.get('sublines');
var title = "Sous axe " + (sublines.length + 1);
......
......@@ -10,6 +10,7 @@ window.W = window.W || {};
appRoutes: {
'': 'home',
'la-notation-w(/)': 'about',
'credits(/)': 'credits',
'partitions(/)': 'scoreList',
'partitions/:id(/)': 'scoreDetail',
'users(/)': 'userList',
......
......@@ -54,7 +54,7 @@ window.W.utils = window.W.utils || {};
if (string in entries) {
return entries[string];
} else {
console.log(string + " n'est pas traduit");
// console.log(string + " n'est pas traduit");
return string;
}
}
......
This diff is collapsed.
......@@ -7,6 +7,12 @@
<title>Notation W</title>
<link rel="icon" type="image/png" href="{% static 'playground/img/favicon.png' %}">
<link rel=icon href="{% static 'playground/img/favicon.png' %}" sizes="32x32" type="image/png">
<link rel=icon href="{% static 'playground/img/favicon-192.png' %}" sizes="192x192" type="image/png">
<link rel="stylesheet" href="{% static 'playground/vendors/reset.css' %}">
<link rel="stylesheet" href="{% static 'playground/vendors/backbone.autocomplete.css' %}">
......@@ -18,6 +24,10 @@
{% include "playground/underscore/about.mtpl" %}
</script>
<script id="credits-template" type="text/template">
{% include "playground/underscore/credits.mtpl" %}
</script>
<script id="applied-filters-template" type="text/template">
{% include "playground/underscore/applied-filters.mtpl" %}
</script>
......@@ -159,6 +169,10 @@
{% include "playground/underscore/search.mtpl" %}
</script>
<script id="share-with-template" type="text/template">
{% include "playground/underscore/share-with.mtpl" %}
</script>
<script id="slider-template" type="text/template">
{% include "playground/underscore/slider.mtpl" %}
</script>
......@@ -190,6 +204,7 @@
<script src="{% static 'playground/vendors/moment-with-locales.min.js' %}"></script>
<script src="{% static 'playground/vendors/jquery-ui.min.js' %}"></script>
<script src="{% static 'playground/vendors/jquery.mjs.nestedSortable.js' %}"></script>
<script src="{% static 'playground/vendors/jquery.autocomplete.min.js' %}"></script>
<script src="{% static 'playground/js/i18n/fr.js' %}"></script>
<script src="{% static 'playground/js/i18n/en.js' %}"></script>
......
{% load flatpages %}
{% get_flatpages '/credits/' as credits_pages %}
{% for page in credits_pages %}
{{ page.content|safe }}
{% endfor %}
......@@ -13,15 +13,15 @@
</ul>
</nav>
<div id="create"></div>
<div id="create" class="main-header__create create"></div>
<nav class="main-header__user">
<% if (username) { %>
<% if (isLoggedIn) { %>
<%- t('Bienvenue') %> <a href="/compte"><%- username %></a> <a href="#" class="js-logout">(←)</a>
<% } %>
<ul>
<% if (username) { %>
<% if (isLoggedIn) { %>
<!-- <li><a href="#" class="js-logout"><%- t('Se déconnecter') %></a></li> -->
<% } else { %>
<li><a href="#" class="js-login"><%- t('Se connecter') %></a> <!--<a href="#" class="js-register"><%- t('S’inscrire') %></a>--></li>
......@@ -36,7 +36,7 @@
<% } else { %>
<li class="menu__item"><a href="#" class="js-lang" data-lang="fr">Français</a></li>
<% } %>
<li class="menu__item"><a href="#"><%- t('Crédits') %></a></li>
<li class="menu__item"><a href="/credits/"><%- t('Crédits') %></a></li>
<li class="menu__item"><a href="http://1110111.org"><%- t('Site W') %></a></li>
</ul>
</nav>
......
......@@ -60,5 +60,13 @@
<input type="text" name="language" value="<%- (language) ? language : W.utils.getUserLanguage() %>">
</label>
<!-- Permissions -->
<label>
<span class="label-text"><%- t('Est public?') %>: </span>
<input type="checkbox" name="is_public" <% if (is_public) { %>checked<% } %>>
</label>
<div id="share_with"></div>
<button data-name="close"><%- t('Annuler') %></button>
<button data-name="submit">OK</button>
......@@ -106,6 +106,23 @@
<dd class="score-meta__def"><%- formatTimestamp(updated_at) %></dd>
</dl>
{% comment %}
<dl class="score-meta">
<dt class="score-meta__term score-meta__term--inline"><%- t('Créé par') %></dt>
<dd class="score-meta__def"><%- created_by.username %></dd>
<dt class="score-meta__term score-meta__term--inline"><%- t('Partagé avec') %></dt>
<dd class="score-meta__def">
<ul>
<% for (var i=0; i < shared_with.length; i++) { %>
<li><%- shared_with[i].username %></li>
<% } %>
</ul>
</dd>
<dt class="score-meta__term score-meta__term--inline"><%- t('Est public?') %></dt>
<dd class="score-meta__def"><%- is_public %></dd>
</dl>
{% endcomment %}
<div id="permissions"></div>
<form></form>
......
<p><%- t('Classer') %>: <span class="js-sort-by-title"><%- t('Par titre') %></span> <span class="js-sort-by-date"><%- t('Par date') %></span></p>
<br>
<h1 class="search__heading">Recherche</h1>
<form class="search__form panel-form" action="search_submit" method="get" accept-charset="utf-8">
<input class="search__textfield search-input panel-form__textfield" type="text" placeholder="<%- t('Titre ou auteur') %>">
......
<input type="text" name="country" id="autocomplete"/>
<div id="user-list"></div>
<h2>Partitions partagées avec vous</h2>
<div id="list"></div>
from django.views.generic.base import TemplateView
from rest_framework import viewsets, permissions, authentication
from rest_framework import viewsets, permissions
from rest_framework.authentication import SessionAuthentication
from rest_framework.pagination import PageNumberPagination
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser
from rest_framework.filters import OrderingFilter, SearchFilter, DjangoObjectPermissionsFilter
from rest_framework.filters import OrderingFilter, SearchFilter, DjangoObjectPermissionsFilter, BaseFilterBackend
from rest_framework.decorators import action
from django_filters import BooleanFilter
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from .models import Attachment, Score, FeaturedScore
from .serializers import AttachmentSerializer, ScoreSerializer, ScoreLightSerializer, UserSerializer, FlatPageSerializer
from guardian.shortcuts import get_anonymous_user
from django.contrib.auth.models import User
from guardian.shortcuts import get_objects_for_user
# from django.contrib.auth import get_user
### from guardian.shortcuts import get_anonymous_user
### from guardian.shortcuts import get_objects_for_user
from rest_framework.response import Response
from collections import OrderedDict
from django.db import models
from django.db.models import Q
from playground.models import Score
from taggit.models import Tag
from django.contrib.flatpages.models import FlatPage
......@@ -61,29 +64,6 @@ class ScoreFilter(FilterSet):
fields = ('title', 'score_type', 'is_featured', 'shared_with', 'language', 'tags')
class ScoreAuthentication(authentication.SessionAuthentication):
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns AnonymousUser if no user is supplied, or `None` if
user is inactive.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(request._request, 'user', None)
# Unauthenticated, CSRF validation not required
if not user:
return (get_anonymous_user(), None)
elif not user.is_active:
return None
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)
class ScorePermissions(permissions.DjangoObjectPermissions):
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
......@@ -101,6 +81,34 @@ class ScorePermissions(permissions.DjangoObjectPermissions):
}
class ScorePermission(permissions.BasePermission):
"""
Object-level permission to only allow owners or guests of an object to edit it.
Assumes the model instance has `created_by`, `shared_with` and `is_public` attributes.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request if score is public,
if obj.is_public and request.method in permissions.SAFE_METHODS:
return True
else:
return obj.created_by == request.user or request.user in obj.shared_with.all()
class ScoreFilterBackend(BaseFilterBackend):
"""
Filter that only allows users to see their own objects.
"""
def filter_queryset(self, request, queryset, view):
# returns Public scores only for Anonymous users
if request.user.is_anonymous():
return queryset.filter(is_public=True)
else:
return queryset.filter(Q(is_public=True)
| Q(created_by=request.user)
| Q(shared_with=request.user)).distinct()
class ScoreViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
......@@ -108,9 +116,9 @@ class ScoreViewSet(viewsets.ModelViewSet):
_ignore_model_permissions = True # Seems essential to django guardian
queryset = Score.objects.all()
pagination_class = ScoreViewSetPagination
filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter, DjangoObjectPermissionsFilter)
authentication_classes = (ScoreAuthentication,)
permission_classes = (ScorePermissions,)
filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter, ScoreFilterBackend)
authentication_classes = (SessionAuthentication,)
permission_classes = (ScorePermission,)
search_fields = ('title', 'score_author', 'performance_author')
# filter_fields = ('title', 'score_type')
filter_class = ScoreFilter
......@@ -148,17 +156,27 @@ class ScoreViewSet(viewsets.ModelViewSet):
"""
queryset = super(ScoreViewSet, self).get_queryset()
can_edit = self.request.query_params.get('can_edit', None)
if can_edit is not None:
if (self.request.user.is_anonymous()):
# Do not return scores for AnonymousUser
queryset = queryset.none()
else:
queryset = queryset.filter(Q(created_by=self.request.user)
| Q(shared_with=self.request.user)).distinct()
shared_with = self.request.query_params.get('shared_with', None)
if shared_with is not None:
# Do not return scores for AnonymousUser
if (self.request.user.is_anonymous()):
return queryset.none()
return get_objects_for_user(self.request.user, 'playground.view_score')
# Do not return scores for AnonymousUser
queryset = queryset.none()
else:
queryset = queryset.filter(shared_with=self.request.user)
is_featured= self.request.query_params.get('is_featured', None)
if is_featured is not None:
ids = FeaturedScore.objects.all().order_by('-order').values_list("score__id", flat=True)
return queryset.filter(id__in=ids)
queryset = queryset.filter(id__in=ids)
return queryset
......
Markdown is supported
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