Move permissions form building to JS

This commit is contained in:
Yohan Boniface 2018-06-15 23:25:38 +02:00
parent 6b3d45064c
commit 48185650b0
18 changed files with 368 additions and 156 deletions

View file

@ -1,4 +1,5 @@
Django==2.0.5 Django==2.0.5
django-agnocomplete==0.12.2
django-compressor==2.1.1 django-compressor==2.1.1
Pillow==5.1.0 Pillow==5.1.0
psycopg2==2.7.4 psycopg2==2.7.4

19
umap/autocomplete.py Normal file
View file

@ -0,0 +1,19 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.urls import reverse
from agnocomplete.register import register
from agnocomplete.core import AgnocompleteModel
@register
class AutocompleteUser(AgnocompleteModel):
model = get_user_model()
fields = ['^username']
def item(self, current_item):
data = super().item(current_item)
data['url'] = reverse(settings.USER_MAPS_URL,
args=(current_item.get_username(), ))
return data

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from django import forms from django import forms
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -28,7 +26,6 @@ class FlatErrorList(ErrorList):
class UpdateMapPermissionsForm(forms.ModelForm): class UpdateMapPermissionsForm(forms.ModelForm):
owner = forms.ModelChoiceField(User.objects, required=True)
class Meta: class Meta:
model = Map model = Map

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import os import os
import time import time
@ -175,6 +173,8 @@ class Map(NamedModel):
and self.is_anonymous_owner(request)): and self.is_anonymous_owner(request)):
can = True can = True
if user and user.is_authenticated: if user and user.is_authenticated:
# TODO: only when using the anonymous-edit URL with an
# authenticated user
# if user is authenticated, attach as owner # if user is authenticated, attach as owner
self.owner = user self.owner = user
self.save() self.save()

View file

@ -78,6 +78,7 @@ INSTALLED_APPS = (
'umap', 'umap',
'compressor', 'compressor',
'social_django', 'social_django',
'agnocomplete',
) )
# ============================================================================= # =============================================================================
@ -159,8 +160,6 @@ MIDDLEWARE = (
# ============================================================================= # =============================================================================
ENABLE_ACCOUNT_LOGIN = False ENABLE_ACCOUNT_LOGIN = False
AUTHENTICATION_BACKENDS += (
)
# ============================================================================= # =============================================================================
# Miscellaneous project settings # Miscellaneous project settings

View file

@ -12,8 +12,10 @@ L.U.AutoComplete = L.Class.extend({
RESULTS: [], RESULTS: [],
initialize: function (el, options) { initialize: function (el, options) {
this.el = L.DomUtil.get(el); this.el = el;
L.setOptions(options); var ui = new L.U.UI(document.querySelector('header'));
this.xhr = new L.U.Xhr(ui);
L.setOptions(this, options);
var CURRENT = null; var CURRENT = null;
try { try {
Object.defineProperty(this, 'CURRENT', { Object.defineProperty(this, 'CURRENT', {
@ -37,9 +39,9 @@ L.U.AutoComplete = L.Class.extend({
this.input = L.DomUtil.element('input', { this.input = L.DomUtil.element('input', {
type: 'text', type: 'text',
placeholder: this.options.placeholder, placeholder: this.options.placeholder,
autocomplete: 'off' autocomplete: 'off',
}); className: this.options.className
L.DomUtil.before(this.el, this.input); }, this.el);
L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this); L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this);
L.DomEvent.on(this.input, 'keyup', this.onKeyUp, this); L.DomEvent.on(this.input, 'keyup', this.onKeyUp, this);
L.DomEvent.on(this.input, 'blur', this.onBlur, this); L.DomEvent.on(this.input, 'blur', this.onBlur, this);
@ -63,10 +65,7 @@ L.U.AutoComplete = L.Class.extend({
onKeyDown: function (e) { onKeyDown: function (e) {
switch (e.keyCode) { switch (e.keyCode) {
case L.U.Keys.TAB: case L.U.Keys.TAB:
if(this.CURRENT !== null) if(this.CURRENT !== null) this.setChoice();
{
this.setChoice();
}
L.DomEvent.stop(e); L.DomEvent.stop(e);
break; break;
case L.U.Keys.ENTER: case L.U.Keys.ENTER:
@ -149,8 +148,8 @@ L.U.AutoComplete = L.Class.extend({
setChoice: function (choice) { setChoice: function (choice) {
choice = choice || this.RESULTS[this.CURRENT]; choice = choice || this.RESULTS[this.CURRENT];
if (choice) { if (choice) {
this.input.value = choice.display; this.input.value = choice.item.label;
this.select(choice); this.options.on_select(choice);
this.displaySelected(choice); this.displaySelected(choice);
this.hide(); this.hide();
if (this.options.callback) { if (this.options.callback) {
@ -165,26 +164,18 @@ L.U.AutoComplete = L.Class.extend({
this.clear(); this.clear();
return; return;
} }
if(!val) { if( val + '' === this.CACHE + '') return;
this.clear(); else this.CACHE = val;
return; this._do_search(val, (data) => {
} this.handleResults(data.data);
if( val + '' === this.CACHE + '') { });
return;
}
else {
this.CACHE = val;
}
var results = this._do_search(val);
return this.handleResults(results);
}, },
createResult: function (item) { createResult: function (item) {
var el = L.DomUtil.element('li', {}, this.container); var el = L.DomUtil.element('li', {}, this.container);
el.innerHTML = item.display; el.innerHTML = item.label;
var result = { var result = {
value: item.value, item: item,
display: item.display,
el: el el: el
}; };
L.DomEvent.on(el, 'mouseover', function () { L.DomEvent.on(el, 'mouseover', function () {
@ -223,12 +214,12 @@ L.U.AutoComplete = L.Class.extend({
highlight: function () { highlight: function () {
var self = this; var self = this;
this.forEach(this.RESULTS, function (item, index) { this.forEach(this.RESULTS, function (result, index) {
if (index === self.CURRENT) { if (index === self.CURRENT) {
L.DomUtil.addClass(item.el, 'on'); L.DomUtil.addClass(result.el, 'on');
} }
else { else {
L.DomUtil.removeClass(item.el, 'on'); L.DomUtil.removeClass(result.el, 'on');
} }
}); });
}, },
@ -260,114 +251,69 @@ L.U.AutoComplete = L.Class.extend({
}); });
L.U.AutoComplete.BaseSelect = L.U.AutoComplete.extend({ L.U.AutoComplete.Ajax = L.U.AutoComplete.extend({
initialize: function (el, options) { initialize: function (el, options) {
L.U.AutoComplete.prototype.initialize.call(this, el, options); L.U.AutoComplete.prototype.initialize.call(this, el, options);
if (!this.el) return this; if (!this.el) return this;
this.el.style.display = 'none';
this.createInput(); this.createInput();
this.createContainer(); this.createContainer();
this.initSelectedContainer(); this.selected_container = this.initSelectedContainer();
}, },
optionToResult: function (option) { optionToResult: function (option) {
return { return {
value: option.value, value: option.value,
display: option.innerHTML label: option.innerHTML
}; };
}, },
_do_search: function (val) { _do_search: function (val, callback) {
var results = [],
self = this,
count = 0;
val = val.toLowerCase(); val = val.toLowerCase();
this.forEach(this.el, function (item) { this.xhr.get('/agnocomplete/AutocompleteUser/?q=' + encodeURIComponent(val), {callback: callback});
var candidate = item.innerHTML.toLowerCase();
if (candidate === val || (candidate.indexOf(val) !== -1 && !item.selected && count < self.options.maxResults)) {
results.push(self.optionToResult(item));
count++;
}
});
return results;
},
select: function (option) {
this.forEach(this.el, function (item) {
if (item.value == option.value) {
item.selected = true;
}
});
},
unselect: function (option) {
this.forEach(this.el, function (item) {
if (item.value == option.value) {
item.selected = false;
}
});
} }
}); });
L.U.AutoComplete.MultiSelect = L.U.AutoComplete.BaseSelect.extend({ L.U.AutoComplete.Ajax.SelectMultiple = L.U.AutoComplete.Ajax.extend({
initSelectedContainer: function () { initSelectedContainer: function () {
this.selected_container = L.DomUtil.after(this.input, L.DomUtil.element('ul', {className: 'umap-multiresult'})); return L.DomUtil.after(this.input, L.DomUtil.element('ul', {className: 'umap-multiresult'}));
var self = this;
this.forEach(this.el, function (option) {
if (option.selected) {
self.displaySelected(self.optionToResult(option));
}
});
}, },
displaySelected: function (result) { displaySelected: function (result) {
var result_el = L.DomUtil.element('li', {}, this.selected_container); var result_el = L.DomUtil.element('li', {}, this.selected_container);
result_el.innerHTML = result.display; result_el.innerHTML = result.item.label;
var close = L.DomUtil.element('span', {className: 'close'}, result_el); var close = L.DomUtil.element('span', {className: 'close'}, result_el);
close.innerHTML = '×'; close.innerHTML = '×';
L.DomEvent.on(close, 'click', function () { L.DomEvent.on(close, 'click', function () {
this.selected_container.removeChild(result_el); this.selected_container.removeChild(result_el);
this.unselect(result); this.options.on_unselect(result);
}, this); }, this);
this.hide(); this.hide();
} }
}); });
L.U.AutoComplete.multiSelect = function (el, options) {
return new L.U.AutoComplete.MultiSelect(el, options);
};
L.U.AutoComplete.Ajax.Select = L.U.AutoComplete.Ajax.extend({
L.U.AutoComplete.Select = L.U.AutoComplete.BaseSelect.extend({
initSelectedContainer: function () { initSelectedContainer: function () {
this.selected_container = L.DomUtil.after(this.input, L.DomUtil.element('div', {className: 'umap-singleresult'})); return L.DomUtil.after(this.input, L.DomUtil.element('div', {className: 'umap-singleresult'}));
var self = this;
if (this.el.selectedIndex !== -1 && this.el[this.el.selectedIndex].value !== '') {
self.displaySelected(self.optionToResult(this.el[this.el.selectedIndex]));
}
}, },
displaySelected: function (result) { displaySelected: function (result) {
var result_el = L.DomUtil.element('div', {}, this.selected_container); var result_el = L.DomUtil.element('div', {}, this.selected_container);
result_el.innerHTML = result.display; result_el.innerHTML = result.item.label;
var close = L.DomUtil.element('span', {className: 'close'}, result_el); var close = L.DomUtil.element('span', {className: 'close'}, result_el);
close.innerHTML = '×'; close.innerHTML = '×';
this.input.style.display = 'none'; this.input.style.display = 'none';
L.DomEvent.on(close, 'click', function () { L.DomEvent.on(close, 'click', function () {
this.selected_container.innerHTML = ''; this.selected_container.innerHTML = '';
this.unselect(result); this.options.on_unselect(result);
this.input.style.display = 'block'; this.input.style.display = 'block';
}, this); }, this);
this.hide(); this.hide();
} }
}); });
L.U.AutoComplete.select = function (el, options) {
return new L.U.AutoComplete.Select(el, options);
};

View file

@ -88,7 +88,7 @@ L.U.UpdatePermsAction = L.U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.updatePermissions(); this.map.permissions.panel();
} }
}); });

View file

@ -322,6 +322,7 @@ L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({
}); });
L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({ L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({
selectOptions: [ selectOptions: [
[undefined, L._('inherit')], [undefined, L._('inherit')],
@ -601,6 +602,61 @@ L.FormBuilder.Range = L.FormBuilder.Input.extend({
}); });
L.FormBuilder.ManageOwner = L.FormBuilder.Element.extend({
build: function () {
var options = {className: 'edit-owner'};
options.on_select = (choice) => {
this._value = {
'id': choice.item.value,
'name': choice.item.label,
'url': choice.item.url
};
this.set();
}
this.autocomplete = new L.U.AutoComplete.Ajax.Select(this.parentNode, options);
var owner = this.toHTML();
if (owner) this.autocomplete.displaySelected({'item': {'value': owner.id, 'label': owner.name}});
},
value: function () {
return this._value;
}
});
L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
build: function () {
var options = {className: 'edit-editors'};
options.on_select = (choice) => {
this._values.push({
'id': choice.item.value,
'name': choice.item.label,
'url': choice.item.url
});
this.set();
}
options.on_unselect = (choice) => {
var index = this._values.findIndex((item) => item.id === choice.item.value);
if (index !== -1) {
this._values.splice(index, 1);
this.set();
}
}
this.autocomplete = new L.U.AutoComplete.Ajax.SelectMultiple(this.parentNode, options);
this._values = this.toHTML();
if (this._values) for (var i = 0; i < this._values.length; i++) this.autocomplete.displaySelected({'item': {'value': this._values[i].id, 'label': this._values[i].name}});
},
value: function () {
return this._values;
}
});
L.U.FormBuilder = L.FormBuilder.extend({ L.U.FormBuilder = L.FormBuilder.extend({
options: { options: {

View file

@ -198,6 +198,7 @@ L.U.Map.include({
this.help = new L.U.Help(this); this.help = new L.U.Help(this);
this.slideshow = new L.U.Slideshow(this, this.options.slideshow); this.slideshow = new L.U.Slideshow(this, this.options.slideshow);
this.permissions = new L.U.MapPermissions(this, this.options.permissions);
this.initCaptionBar(); this.initCaptionBar();
if (this.options.allowEdit) { if (this.options.allowEdit) {
this.editTools = new L.U.Editable(this); this.editTools = new L.U.Editable(this);
@ -235,9 +236,9 @@ L.U.Map.include({
L.U.EditPropertiesAction, L.U.EditPropertiesAction,
L.U.ChangeTileLayerAction, L.U.ChangeTileLayerAction,
L.U.ManageDatalayersAction, L.U.ManageDatalayersAction,
L.U.UpdateExtentAction L.U.UpdateExtentAction,
L.U.UpdatePermsAction
]; ];
if (this.options.urls.map_update_permissions) editActions.push(L.U.UpdatePermsAction);
new L.U.SettingsToolbar({actions: editActions}).addTo(this); new L.U.SettingsToolbar({actions: editActions}).addTo(this);
} }
this._controls.zoom = new L.Control.Zoom({zoomInTitle: L._('Zoom in'), zoomOutTitle: L._('Zoom out')}); this._controls.zoom = new L.Control.Zoom({zoomInTitle: L._('Zoom in'), zoomOutTitle: L._('Zoom out')});
@ -345,11 +346,13 @@ L.U.Map.include({
this._backupOptions = L.extend({}, this.options); this._backupOptions = L.extend({}, this.options);
this._backupOptions.tilelayer = L.extend({}, this.options.tilelayer); this._backupOptions.tilelayer = L.extend({}, this.options.tilelayer);
this._backupOptions.limitBounds = L.extend({}, this.options.limitBounds); this._backupOptions.limitBounds = L.extend({}, this.options.limitBounds);
this._backupOptions.permissions = L.extend({}, this.permissions.options);
}, },
resetOptions: function () { resetOptions: function () {
this.options = L.extend({}, this._backupOptions); this.options = L.extend({}, this._backupOptions);
this.options.tilelayer = L.extend({}, this._backupOptions.tilelayer); this.options.tilelayer = L.extend({}, this._backupOptions.tilelayer);
this.permissions.options = L.extend({}, this._backupOptions.permissions);
}, },
initShortcuts: function () { initShortcuts: function () {
@ -676,15 +679,6 @@ L.U.Map.include({
return geojson; return geojson;
}, },
updatePermissions: function () {
if (!this.options.umap_id) return this.ui.alert({content: L._('Please save the map before'), level: 'info'});
var url = L.Util.template(this.options.urls.map_update_permissions, {'map_id': this.options.umap_id});
this.get(url, {
listen_form: {'id': 'map_edit'},
className: 'dark'
});
},
importPanel: function () { importPanel: function () {
var container = L.DomUtil.create('div', 'umap-upload'), var container = L.DomUtil.create('div', 'umap-upload'),
title = L.DomUtil.create('h4', '', container), title = L.DomUtil.create('h4', '', container),
@ -862,13 +856,7 @@ L.U.Map.include({
var container = L.DomUtil.create('div', 'umap-caption'), var container = L.DomUtil.create('div', 'umap-caption'),
title = L.DomUtil.create('h3', '', container); title = L.DomUtil.create('h3', '', container);
title.innerHTML = this.options.name; title.innerHTML = this.options.name;
if (this.options.author && this.options.author.name && this.options.author.link) { this.permissions.addOwnerLink('h5', container);
var authorContainer = L.DomUtil.add('h5', 'umap-map-author', container, L._('by') + ' '),
author = L.DomUtil.create('a');
author.href = this.options.author.link;
author.innerHTML = this.options.author.name;
authorContainer.appendChild(author);
}
if (this.options.description) { if (this.options.description) {
var description = L.DomUtil.create('div', 'umap-map-description', container); var description = L.DomUtil.create('div', 'umap-map-description', container);
description.innerHTML = L.Util.toHTML(this.options.description); description.innerHTML = L.Util.toHTML(this.options.description);
@ -1079,6 +1067,7 @@ L.U.Map.include({
formData.append('settings', JSON.stringify(geojson)); formData.append('settings', JSON.stringify(geojson));
this.post(this.getSaveUrl(), { this.post(this.getSaveUrl(), {
data: formData, data: formData,
context: this,
callback: function (data) { callback: function (data) {
var duration = 3000; var duration = 3000;
if (!this.options.umap_id) { if (!this.options.umap_id) {
@ -1094,9 +1083,8 @@ L.U.Map.include({
this.ui.alert({content: msg, level: 'info', duration: duration}); this.ui.alert({content: msg, level: 'info', duration: duration});
}); });
this.ui.closePanel(); this.ui.closePanel();
this.continueSaving(); this.permissions.save();
}, }
context: this
}); });
}, },
@ -1376,13 +1364,7 @@ L.U.Map.include({
var container = L.DomUtil.create('div', 'umap-caption-bar', this._controlContainer), var container = L.DomUtil.create('div', 'umap-caption-bar', this._controlContainer),
name = L.DomUtil.create('h3', '', container); name = L.DomUtil.create('h3', '', container);
L.DomEvent.disableClickPropagation(container); L.DomEvent.disableClickPropagation(container);
if (this.options.author && this.options.author.name && this.options.author.link) { this.permissions.addOwnerLink('span', container);
var authorContainer = L.DomUtil.add('span', 'umap-map-author', container, ' ' + L._('by') + ' '),
author = L.DomUtil.create('a');
author.href = this.options.author.link;
author.innerHTML = this.options.author.name;
authorContainer.appendChild(author);
}
var about = L.DomUtil.add('a', 'umap-about-link', container, ' — ' + L._('About')); var about = L.DomUtil.add('a', 'umap-about-link', container, ' — ' + L._('About'));
about.href = '#'; about.href = '#';
L.DomEvent.on(about, 'click', this.displayCaption, this); L.DomEvent.on(about, 'click', this.displayCaption, this);

View file

@ -0,0 +1,119 @@
// Dedicated object so we can deal with a separate dirty status, and thus
// call the endpoint only when needed, saving one call at each save.
L.U.MapPermissions = L.Class.extend({
options: {
owner: null,
editors: [],
share_status: null,
edit_status: null
},
initialize: function (map) {
this.options = map.options.permissions || {};
this.map = map;
var isDirty = false,
self = this;
try {
Object.defineProperty(this, 'isDirty', {
get: function () {
return isDirty;
},
set: function (status) {
isDirty = status;
if (status) self.map.isDirty = status;
}
});
}
catch (e) {
// Certainly IE8, which has a limited version of defineProperty
}
},
isOwner: function () {
return this.map.options.user && this.options.owner && this.map.options.user.id == this.options.owner.id;
},
isAnonymousMap: function () {
return !this.options.owner;
},
getMap: function () {
return this.map;
},
panel: function () {
if (!this.map.options.umap_id) return this.map.ui.alert({content: L._('Please save the map before'), level: 'info'});
var container = L.DomUtil.create('div', 'permissions-panel'),
fields = [],
title = L.DomUtil.create('h4', '', container);
if (this.isAnonymousMap()) {
if (this.map.options.anonymous_edit_url) {
var helpText = L._('Secret edit link is:<br>{link}', {link: this.map.options.anonymous_edit_url});
fields.push(['options.edit_status', {handler: 'IntSelect', label: L._('Who can edit'), selectOptions: this.map.options.anonymous_edit_statuses, helpText: helpText}]);
}
} else {
if (this.isOwner()) {
fields.push(['options.edit_status', {handler: 'IntSelect', label: L._('Who can edit'), selectOptions: this.map.options.edit_statuses}]);
fields.push(['options.share_status', {handler: 'IntSelect', label: L._('Who can view'), selectOptions: this.map.options.share_statuses}]);
fields.push(['options.owner', {handler: 'ManageOwner', label: L._("Map's owner")}]);
}
fields.push(['options.editors', {handler: 'ManageEditors', label: L._("Map's editors")}]);
}
title.innerHTML = L._('Update permissions');
var builder = new L.U.FormBuilder(this, fields);
var form = builder.build();
container.appendChild(form);
this.map.ui.openPanel({data: {html: container}, className: 'dark'});
},
anonymousMapPanel: function () {
var container = L.DomUtil.create('div'),
fields = [],
title = L.DomUtil.create('h4', '', container);
fields.push(['options.edit_status', {handler: 'IntSelect', label: L._('Who can edit'), selectOptions: this.map.options.edit_statuses}]);
title.innerHTML = L._('Update permissions');
var builder = new L.U.FormBuilder(this, fields);
var form = builder.build();
container.appendChild(form);
this.map.ui.openPanel({data: {html: container}, className: 'dark'});
},
save: function () {
if (!this.isDirty) return this.map.continueSaving();
var formData = new FormData();
if (!this.isAnonymousMap() && this.options.editors) {
const editors = this.options.editors.map((u) => u.id);
for (var i = 0; i < this.options.editors.length; i++) formData.append('editors', this.options.editors[i].id);
}
if (this.isOwner() || this.isAnonymousMap()) formData.append('edit_status', this.options.edit_status);
if (this.isOwner()) {
formData.append('owner', this.options.owner && this.options.owner.id);
formData.append('share_status', this.options.share_status);
}
this.map.post(this.getUrl(), {
data: formData,
context: this,
callback: function (data) {
this.isDirty = false;
this.map.continueSaving();
}
});
},
getUrl: function () {
return L.Util.template(this.map.options.urls.map_update_permissions, {'map_id': this.map.options.umap_id});
},
addOwnerLink: function (element, container) {
if (this.options.owner && this.options.owner.name && this.options.owner.url) {
var ownerContainer = L.DomUtil.add(element, 'umap-map-owner', container, ' ' + L._('by') + ' '),
owner = L.DomUtil.create('a');
owner.href = this.options.owner.url;
owner.innerHTML = this.options.owner.name;
ownerContainer.appendChild(owner);
}
}
});

View file

@ -1,4 +1,4 @@
describe('L.Utorage.Map', function(){ describe('L.Umap.Map', function(){
before(function () { before(function () {
this.server = sinon.fakeServer.create(); this.server = sinon.fakeServer.create();

View file

@ -0,0 +1,77 @@
describe('L.Permissions', function () {
var path = '/map/99/datalayer/edit/62/';
before(function () {
this.server = sinon.fakeServer.create();
this.server.respondWith('GET', '/datalayer/62/', JSON.stringify(RESPONSES.datalayer62_GET));
this.map = initMap({umap_id: 99});
this.datalayer = this.map.getDataLayerByUmapId(62);
this.server.respond();
enableEdit();
});
after(function () {
clickCancel();
this.server.restore();
resetMap();
});
describe('#open()', function () {
var button;
it('should exist update permissions link', function () {
button = qs('a.update-map-permissions');
expect(button).to.be.ok;
});
it('should open table button click', function () {
happen.click(button);
expect(qs('.permissions-panel')).to.be.ok;
});
});
describe('#anonymous with cookie', function () {
var button;
it('should only allow edit_status', function () {
this.map.options.anonymous_edit_url = 'http://anonymous.url'
button = qs('a.update-map-permissions');
happen.click(button);
expect(qs('select[name="edit_status"]')).to.be.ok;
expect(qs('select[name="share_status"]')).not.to.be.ok;
expect(qs('input.edit-owner')).not.to.be.ok;
});
});
describe('#editor', function () {
var button;
it('should only allow editors', function () {
this.map.permissions.options.owner = {id: 1, url: '/url', name: 'jojo'};
button = qs('a.update-map-permissions');
happen.click(button);
expect(qs('select[name="edit_status"]')).not.to.be.ok;
expect(qs('select[name="share_status"]')).not.to.be.ok;
expect(qs('input.edit-owner')).not.to.be.ok;
expect(qs('input.edit-editors')).to.be.ok;
});
});
describe('#owner', function () {
var button;
it('should allow everything', function () {
this.map.permissions.options.owner = {id: 1, url: '/url', name: 'jojo'};
this.map.options.user = {id: 1, url: '/url', name: 'jojo'};
button = qs('a.update-map-permissions');
happen.click(button);
expect(qs('select[name="edit_status"]')).to.be.ok;
expect(qs('select[name="share_status"]')).to.be.ok;
expect(qs('input.edit-owner')).to.be.ok;
expect(qs('input.edit-editors')).to.be.ok;
});
});
});

View file

@ -22,6 +22,7 @@
<script src="../vendors/formbuilder/Leaflet.FormBuilder.js"></script> <script src="../vendors/formbuilder/Leaflet.FormBuilder.js"></script>
<script src="../vendors/measurable/Leaflet.Measurable.js"></script> <script src="../vendors/measurable/Leaflet.Measurable.js"></script>
<script src="../js/umap.core.js"></script> <script src="../js/umap.core.js"></script>
<script src="../js/umap.autocomplete.js"></script>
<script src="../js/umap.popup.js"></script> <script src="../js/umap.popup.js"></script>
<script src="../js/umap.xhr.js"></script> <script src="../js/umap.xhr.js"></script>
<script src="../js/umap.forms.js"></script> <script src="../js/umap.forms.js"></script>
@ -31,6 +32,7 @@
<script src="../js/umap.controls.js"></script> <script src="../js/umap.controls.js"></script>
<script src="../js/umap.slideshow.js"></script> <script src="../js/umap.slideshow.js"></script>
<script src="../js/umap.tableeditor.js"></script> <script src="../js/umap.tableeditor.js"></script>
<script src="../js/umap.permissions.js"></script>
<script src="../js/umap.js"></script> <script src="../js/umap.js"></script>
<script src="../js/umap.ui.js"></script> <script src="../js/umap.ui.js"></script>
<link rel="stylesheet" href="../vendors/leaflet/leaflet.css" /> <link rel="stylesheet" href="../vendors/leaflet/leaflet.css" />
@ -41,8 +43,12 @@
<link rel="stylesheet" href="../vendors/contextmenu/leaflet.contextmenu.css" /> <link rel="stylesheet" href="../vendors/contextmenu/leaflet.contextmenu.css" />
<link rel="stylesheet" href="../vendors/toolbar/leaflet.toolbar.css" /> <link rel="stylesheet" href="../vendors/toolbar/leaflet.toolbar.css" />
<link rel="stylesheet" href="../vendors/measurable/Leaflet.Measurable.css" /> <link rel="stylesheet" href="../vendors/measurable/Leaflet.Measurable.css" />
<link rel="stylesheet" href="../storage.css" /> <link rel="stylesheet" href="../../umap/font.css">
<link rel="stylesheet" href="../default.css" /> <link rel="stylesheet" href="../../umap/base.css">
<link rel="stylesheet" href="../../umap/content.css">
<link rel="stylesheet" href="../../umap/nav.css">
<link rel="stylesheet" href="../../umap/map.css" />
<link rel="stylesheet" href="../../umap/theme.css">
<script src="../../../../node_modules/sinon/pkg/sinon.js"></script> <script src="../../../../node_modules/sinon/pkg/sinon.js"></script>
<script src="../../../../node_modules/mocha/mocha.js"></script> <script src="../../../../node_modules/mocha/mocha.js"></script>
@ -68,6 +74,7 @@
<script src="./Polygon.js"></script> <script src="./Polygon.js"></script>
<script src="./Util.js"></script> <script src="./Util.js"></script>
<script src="./Controls.js"></script> <script src="./Controls.js"></script>
<script src="./Permissions.js"></script>
<style type="text/css"> <style type="text/css">
#mocha { #mocha {
position: absolute; position: absolute;

View file

@ -38,6 +38,7 @@
<script src="{{ STATIC_URL }}umap/js/umap.controls.js"></script> <script src="{{ STATIC_URL }}umap/js/umap.controls.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.slideshow.js"></script> <script src="{{ STATIC_URL }}umap/js/umap.slideshow.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.tableeditor.js"></script> <script src="{{ STATIC_URL }}umap/js/umap.tableeditor.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.permissions.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.js"></script> <script src="{{ STATIC_URL }}umap/js/umap.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.ui.js"></script> <script src="{{ STATIC_URL }}umap/js/umap.ui.js"></script>
{% endcompress %} {% endcompress %}

View file

@ -18,14 +18,4 @@
{% include "umap/map_init.html" %} {% include "umap/map_init.html" %}
{% endblock %} {% endblock %}
{% include "umap/map_messages.html" %} {% include "umap/map_messages.html" %}
<script type="text/javascript">
MAP.ui.on('panel:ready', function () {
L.U.AutoComplete.multiSelect('id_editors', {
placeholder: "{% trans 'Type editors nick to add…' %}"
});
L.U.AutoComplete.select('id_owner', {
placeholder: "{% trans 'Type new owner nick…' %}"
});
});
</script>
{% endblock %} {% endblock %}

View file

@ -1,7 +0,0 @@
{% load i18n %}
<h3>{% trans "Map permissions" %}</h3>
<form action="{% url 'map_update_permissions' map.pk %}" method="post" id="map_edit">
{% csrf_token %}
{{ form }}
<input type="submit" />
</form>

View file

@ -32,6 +32,7 @@ urlpatterns = [
{'template_name': 'umap/password_change_done.html'}, {'template_name': 'umap/password_change_done.html'},
name='password_change_done'), name='password_change_done'),
url(r'^i18n/', include('django.conf.urls.i18n')), url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^agnocomplete/', include('agnocomplete.urls')),
] ]
i18n_urls = [ i18n_urls = [

View file

@ -339,7 +339,7 @@ class FormLessEditMixin(object):
return simple_json_response(errors=form.errors, return simple_json_response(errors=form.errors,
error=str(form.errors)) error=str(form.errors))
def get_form(self): def get_form(self, form_class=None):
kwargs = self.get_form_kwargs() kwargs = self.get_form_kwargs()
kwargs['error_class'] = FlatErrorList kwargs['error_class'] = FlatErrorList
return self.get_form_class()(**kwargs) return self.get_form_class()(**kwargs)
@ -358,6 +358,11 @@ class MapDetailMixin(object):
'default_iconUrl': "%sumap/img/marker.png" % settings.STATIC_URL, # noqa 'default_iconUrl': "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
'umap_id': self.get_umap_id(), 'umap_id': self.get_umap_id(),
'licences': dict((l.name, l.json) for l in Licence.objects.all()), 'licences': dict((l.name, l.json) for l in Licence.objects.all()),
'edit_statuses': [(i, str(label)) for i, label in Map.EDIT_STATUS],
'share_statuses': [(i, str(label))
for i, label in Map.SHARE_STATUS],
'anonymous_edit_statuses': [(i, str(label)) for i, label
in AnonymousMapPermissionsForm.STATUS],
} }
if self.get_short_url(): if self.get_short_url():
properties['shortUrl'] = self.get_short_url() properties['shortUrl'] = self.get_short_url()
@ -448,12 +453,36 @@ class MapView(MapDetailMixin, DetailView):
map_settings = self.object.settings map_settings = self.object.settings
if "properties" not in map_settings: if "properties" not in map_settings:
map_settings['properties'] = {} map_settings['properties'] = {}
if self.object.owner and hasattr(settings, 'USER_MAPS_URL'): permissions = {}
map_settings['properties']['author'] = { permissions['edit_status'] = self.object.edit_status
permissions['share_status'] = self.object.share_status
if self.object.owner:
permissions['owner'] = {
'id': self.object.owner.pk,
'name': self.object.owner.get_username(), 'name': self.object.owner.get_username(),
'link': reverse(settings.USER_MAPS_URL, 'url': reverse(settings.USER_MAPS_URL,
args=(self.object.owner.get_username(), )) args=(self.object.owner.get_username(), ))
} }
permissions['editors'] = [{
'id': editor.pk,
'name': editor.get_username(),
} for editor in self.object.editors.all()]
map_settings['properties']['permissions'] = permissions
user = self.request.user
if not user.is_anonymous:
map_settings['properties']['user'] = {
'id': user.pk,
'name': user.get_username(),
'url': reverse(settings.USER_MAPS_URL,
args=(user.get_username(), ))
}
if (not self.object.owner
and self.object.is_anonymous_owner(self.request)):
anonymous_url = "%s%s" % (
settings.SITE_URL,
self.object.get_anonymous_edit_url()
)
map_settings['properties']['anonymous_edit_url'] = anonymous_url
return map_settings return map_settings
@ -520,8 +549,7 @@ class MapUpdate(FormLessEditMixin, UpdateView):
) )
class UpdateMapPermissions(UpdateView): class UpdateMapPermissions(FormLessEditMixin, UpdateView):
template_name = "umap/map_update_permissions.html"
model = Map model = Map
pk_url_kwarg = 'map_id' pk_url_kwarg = 'map_id'
@ -532,7 +560,7 @@ class UpdateMapPermissions(UpdateView):
return AnonymousMapPermissionsForm return AnonymousMapPermissionsForm
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super(UpdateMapPermissions, self).get_form(form_class) form = super().get_form(form_class)
user = self.request.user user = self.request.user
if self.object.owner and not user == self.object.owner: if self.object.owner and not user == self.object.owner:
del form.fields['edit_status'] del form.fields['edit_status']
@ -545,10 +573,6 @@ class UpdateMapPermissions(UpdateView):
return simple_json_response( return simple_json_response(
info=_("Map editors updated with success!")) info=_("Map editors updated with success!"))
def render_to_response(self, context, **response_kwargs):
context.update(response_kwargs)
return render_to_json(self.get_template_names(), context, self.request)
class MapDelete(DeleteView): class MapDelete(DeleteView):
model = Map model = Map