mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
fix: make sure anonymous is owner at create (#2189)
The tricky thing is that the Map.is_owner() method check for cookies on the request, but at create this cookie is not set yet on the request, so we have to deal with an exception here. fix #2176
This commit is contained in:
commit
8d59220d05
12 changed files with 94 additions and 96 deletions
|
@ -313,7 +313,7 @@ export const SingleMixin = (Base) =>
|
|||
DomEvent.on(close, 'click', () => {
|
||||
this.selectedContainer.innerHTML = ''
|
||||
this.input.style.display = 'block'
|
||||
this.options.on_unselect(result)
|
||||
this.options.on_unselect?.(result)
|
||||
})
|
||||
this.hide()
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ export const MultipleMixin = (Base) =>
|
|||
})
|
||||
DomEvent.on(close, 'click', () => {
|
||||
this.selectedContainer.removeChild(result_el)
|
||||
this.options.on_unselect(result)
|
||||
this.options.on_unselect?.(result)
|
||||
})
|
||||
this.hide()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,12 @@ export default class Caption {
|
|||
open() {
|
||||
const container = DomUtil.create('div', 'umap-caption')
|
||||
const hgroup = DomUtil.element({ tagName: 'hgroup', parent: container })
|
||||
DomUtil.createTitle(hgroup, this.map.options.name, 'icon-caption icon-block')
|
||||
DomUtil.createTitle(
|
||||
hgroup,
|
||||
this.map.getDisplayName(),
|
||||
'icon-caption icon-block',
|
||||
'map-name'
|
||||
)
|
||||
this.map.addAuthorLink('h4', hgroup)
|
||||
if (this.map.options.description) {
|
||||
const description = DomUtil.element({
|
||||
|
|
|
@ -92,12 +92,12 @@ export class DataLayer {
|
|||
set isDirty(status) {
|
||||
this._isDirty = status
|
||||
if (status) {
|
||||
this.map.addDirtyDatalayer(this)
|
||||
this.map.isDirty = true
|
||||
// A layer can be made dirty by indirect action (like dragging layers)
|
||||
// we need to have it loaded before saving it.
|
||||
if (!this.isLoaded()) this.fetchData()
|
||||
} else {
|
||||
this.map.removeDirtyDatalayer(this)
|
||||
this.map.checkDirty()
|
||||
this.isDeleted = false
|
||||
}
|
||||
}
|
||||
|
@ -339,12 +339,12 @@ export class DataLayer {
|
|||
const id = stamp(this)
|
||||
if (!this.map.datalayers[id]) {
|
||||
this.map.datalayers[id] = this
|
||||
}
|
||||
if (!this.map.datalayers_index.includes(this)) {
|
||||
this.map.datalayers_index.push(this)
|
||||
}
|
||||
this.map.onDataLayersChanged()
|
||||
}
|
||||
}
|
||||
|
||||
_dataUrl() {
|
||||
const template = this.map.options.urls.datalayer_view
|
||||
|
@ -559,7 +559,6 @@ export class DataLayer {
|
|||
|
||||
erase() {
|
||||
this.hide()
|
||||
delete this.map.datalayers[stamp(this)]
|
||||
this.map.datalayers_index.splice(this.getRank(), 1)
|
||||
this.parentPane.removeChild(this.pane)
|
||||
this.map.onDataLayersChanged()
|
||||
|
@ -1029,7 +1028,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
async save() {
|
||||
if (this.isDeleted) return this.saveDelete()
|
||||
if (this.isDeleted) return await this.saveDelete()
|
||||
if (!this.isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
@ -1093,8 +1092,8 @@ export class DataLayer {
|
|||
if (this.umap_id) {
|
||||
await this.map.server.post(this.getDeleteUrl())
|
||||
}
|
||||
delete this.map.datalayers[stamp(this)]
|
||||
this.isDirty = false
|
||||
this.map.continueSaving()
|
||||
}
|
||||
|
||||
getMap() {
|
||||
|
|
|
@ -42,10 +42,6 @@ export class MapPermissions {
|
|||
return !this.map.options.permissions.owner
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return this.map
|
||||
}
|
||||
|
||||
_editAnonymous(container) {
|
||||
const fields = []
|
||||
if (this.isOwner()) {
|
||||
|
@ -182,7 +178,7 @@ export class MapPermissions {
|
|||
}
|
||||
|
||||
async save() {
|
||||
if (!this.isDirty) return this.map.continueSaving()
|
||||
if (!this.isDirty) return
|
||||
const formData = new FormData()
|
||||
if (!this.isAnonymousMap() && this.options.editors) {
|
||||
const editors = this.options.editors.map((u) => u.id)
|
||||
|
@ -205,7 +201,6 @@ export class MapPermissions {
|
|||
if (!error) {
|
||||
this.commit()
|
||||
this.isDirty = false
|
||||
this.map.continueSaving()
|
||||
this.map.fire('postsync')
|
||||
}
|
||||
}
|
||||
|
@ -230,10 +225,12 @@ export class MapPermissions {
|
|||
}
|
||||
|
||||
getShareStatusDisplay() {
|
||||
if (this.map.options.share_statuses) {
|
||||
return Object.fromEntries(this.map.options.share_statuses)[
|
||||
this.options.share_status
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DataLayerPermissions {
|
||||
|
@ -258,7 +255,7 @@ export class DataLayerPermissions {
|
|||
return this._isDirty
|
||||
}
|
||||
|
||||
getMap() {
|
||||
get map() {
|
||||
return this.datalayer.map
|
||||
}
|
||||
|
||||
|
@ -271,7 +268,7 @@ export class DataLayerPermissions {
|
|||
label: translate('Who can edit "{layer}"', {
|
||||
layer: this.datalayer.getName(),
|
||||
}),
|
||||
selectOptions: this.datalayer.map.options.datalayer_edit_statuses,
|
||||
selectOptions: this.map.options.datalayer_edit_statuses,
|
||||
},
|
||||
],
|
||||
]
|
||||
|
@ -283,16 +280,17 @@ export class DataLayerPermissions {
|
|||
}
|
||||
|
||||
getUrl() {
|
||||
return Utils.template(this.datalayer.map.options.urls.datalayer_permissions, {
|
||||
map_id: this.datalayer.map.options.umap_id,
|
||||
return this.map.urls.get('datalayer_permissions', {
|
||||
map_id: this.map.options.umap_id,
|
||||
pk: this.datalayer.umap_id,
|
||||
})
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.isDirty) return this.datalayer.map.continueSaving()
|
||||
if (!this.isDirty) return
|
||||
const formData = new FormData()
|
||||
formData.append('edit_status', this.options.edit_status)
|
||||
const [data, response, error] = await this.datalayer.map.server.post(
|
||||
const [data, response, error] = await this.map.server.post(
|
||||
this.getUrl(),
|
||||
{},
|
||||
formData
|
||||
|
@ -300,7 +298,6 @@ export class DataLayerPermissions {
|
|||
if (!error) {
|
||||
this.commit()
|
||||
this.isDirty = false
|
||||
this.datalayer.map.continueSaving()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -578,11 +578,15 @@ const ControlsMixin = {
|
|||
],
|
||||
|
||||
renderEditToolbar: function () {
|
||||
const container = L.DomUtil.create(
|
||||
const className = 'umap-main-edit-toolbox'
|
||||
const container =
|
||||
document.querySelector(`.${className}`) ||
|
||||
L.DomUtil.create(
|
||||
'div',
|
||||
'umap-main-edit-toolbox with-transition dark',
|
||||
`${className} with-transition dark`,
|
||||
this._controlContainer
|
||||
)
|
||||
container.innerHTML = ''
|
||||
const leftContainer = L.DomUtil.create('div', 'umap-left-edit-toolbox', container)
|
||||
const rightContainer = L.DomUtil.create('div', 'umap-right-edit-toolbox', container)
|
||||
const logo = L.DomUtil.create('div', 'logo', leftContainer)
|
||||
|
@ -623,23 +627,10 @@ const ControlsMixin = {
|
|||
},
|
||||
this
|
||||
)
|
||||
const update = () => {
|
||||
const status = this.permissions.getShareStatusDisplay()
|
||||
nameButton.textContent = this.getDisplayName()
|
||||
// status is not set until map is saved once
|
||||
if (status) {
|
||||
shareStatusButton.textContent = L._('Visibility: {status}', {
|
||||
status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
update()
|
||||
this.once('saved', L.bind(update, this))
|
||||
if (this.options.editMode === 'advanced') {
|
||||
L.DomEvent.on(nameButton, 'click', this.editCaption, this)
|
||||
L.DomEvent.on(shareStatusButton, 'click', this.permissions.edit, this.permissions)
|
||||
}
|
||||
this.on('postsync', L.bind(update, this))
|
||||
if (this.options.user?.id) {
|
||||
L.DomUtil.createLink(
|
||||
'umap-user',
|
||||
|
|
|
@ -141,10 +141,10 @@ L.DomUtil.createButtonIcon = (parent, className, title, callback, size = 16) =>
|
|||
return el
|
||||
}
|
||||
|
||||
L.DomUtil.createTitle = (parent, text, className, tag = 'h3') => {
|
||||
L.DomUtil.createTitle = (parent, text, iconClassName, className = '', tag = 'h3') => {
|
||||
const title = L.DomUtil.create(tag, '', parent)
|
||||
if (className) L.DomUtil.createIcon(title, className)
|
||||
L.DomUtil.add('span', '', title, text)
|
||||
if (className) L.DomUtil.createIcon(title, iconClassName)
|
||||
L.DomUtil.add('span', className, title, text)
|
||||
return title
|
||||
}
|
||||
|
||||
|
|
|
@ -110,10 +110,9 @@ U.Map = L.Map.extend({
|
|||
delete this.options.advancedFilterKey
|
||||
}
|
||||
|
||||
// Global storage for retrieving datalayers and features
|
||||
this.datalayers = {}
|
||||
this.datalayers_index = []
|
||||
this.dirty_datalayers = []
|
||||
// Global storage for retrieving datalayers and features.
|
||||
this.datalayers = {} // All datalayers, including deleted.
|
||||
this.datalayers_index = [] // Datalayers actually on the map and ordered.
|
||||
this.features_index = {}
|
||||
|
||||
// Needed for actions labels
|
||||
|
@ -200,6 +199,7 @@ U.Map = L.Map.extend({
|
|||
this.backup()
|
||||
this.on('click', this.closeInplaceToolbar)
|
||||
this.on('contextmenu', this.onContextMenu)
|
||||
this.propagate()
|
||||
},
|
||||
|
||||
initSyncEngine: async function () {
|
||||
|
@ -231,6 +231,7 @@ U.Map = L.Map.extend({
|
|||
this.renderEditToolbar()
|
||||
this.renderControls()
|
||||
this.browser.redraw()
|
||||
this.propagate()
|
||||
break
|
||||
case 'data':
|
||||
this.redrawVisibleDataLayers()
|
||||
|
@ -487,7 +488,7 @@ U.Map = L.Map.extend({
|
|||
loadDataLayers: async function () {
|
||||
this.datalayersLoaded = true
|
||||
this.fire('datalayersloaded')
|
||||
for (const datalayer of Object.values(this.datalayers)) {
|
||||
for (const datalayer of this.datalayers_index) {
|
||||
if (datalayer.showAtLoad()) await datalayer.show()
|
||||
}
|
||||
this.dataloaded = true
|
||||
|
@ -933,6 +934,7 @@ U.Map = L.Map.extend({
|
|||
if (mustReindex) datalayer.reindex()
|
||||
datalayer.redraw()
|
||||
})
|
||||
this.propagate()
|
||||
this.fire('postsync')
|
||||
this.isDirty = true
|
||||
},
|
||||
|
@ -996,12 +998,12 @@ U.Map = L.Map.extend({
|
|||
if (this.editTools) this.editTools.stopDrawing()
|
||||
this.resetOptions()
|
||||
this.datalayers_index = [].concat(this._datalayers_index_bk)
|
||||
this.dirty_datalayers.slice().forEach((datalayer) => {
|
||||
// Iter over all datalayers, including deleted if any.
|
||||
for (const datalayer of Object.values(this.datalayers)) {
|
||||
if (datalayer.isDeleted) datalayer.connectToMap()
|
||||
datalayer.reset()
|
||||
})
|
||||
if (datalayer.isDirty) datalayer.reset()
|
||||
}
|
||||
this.ensurePanesOrder()
|
||||
this.dirty_datalayers = []
|
||||
this.initTileLayers()
|
||||
this.isDirty = false
|
||||
this.onDataLayersChanged()
|
||||
|
@ -1011,25 +1013,6 @@ U.Map = L.Map.extend({
|
|||
this._container.classList.toggle('umap-is-dirty', this.isDirty)
|
||||
},
|
||||
|
||||
addDirtyDatalayer: function (datalayer) {
|
||||
if (this.dirty_datalayers.indexOf(datalayer) === -1) {
|
||||
this.dirty_datalayers.push(datalayer)
|
||||
this.isDirty = true
|
||||
}
|
||||
},
|
||||
|
||||
removeDirtyDatalayer: function (datalayer) {
|
||||
if (this.dirty_datalayers.indexOf(datalayer) !== -1) {
|
||||
this.dirty_datalayers.splice(this.dirty_datalayers.indexOf(datalayer), 1)
|
||||
this.checkDirty()
|
||||
}
|
||||
},
|
||||
|
||||
continueSaving: function () {
|
||||
if (this.dirty_datalayers.length) this.dirty_datalayers[0].save()
|
||||
else this.fire('saved')
|
||||
},
|
||||
|
||||
exportOptions: function () {
|
||||
const properties = {}
|
||||
for (const option of Object.keys(U.SCHEMA)) {
|
||||
|
@ -1059,19 +1042,17 @@ U.Map = L.Map.extend({
|
|||
return
|
||||
}
|
||||
if (data.login_required) {
|
||||
window.onLogin = () => this.saveSelf()
|
||||
window.onLogin = () => this.save()
|
||||
window.open(data.login_required)
|
||||
return
|
||||
}
|
||||
if (data.user?.id) {
|
||||
this.options.user = data.user
|
||||
this.renderEditToolbar()
|
||||
}
|
||||
if (!this.options.umap_id) {
|
||||
this.options.umap_id = data.id
|
||||
this.permissions.setOptions(data.permissions)
|
||||
this.permissions.commit()
|
||||
if (data?.permissions?.anonymous_edit_url) {
|
||||
if (data.permissions?.anonymous_edit_url) {
|
||||
this.once('saved', () => {
|
||||
U.AlertCreation.info(
|
||||
L._('Your map has been created with an anonymous account!'),
|
||||
|
@ -1104,21 +1085,42 @@ U.Map = L.Map.extend({
|
|||
} else {
|
||||
window.location = data.url
|
||||
}
|
||||
this.permissions.save()
|
||||
this.propagate()
|
||||
return true
|
||||
},
|
||||
|
||||
save: function () {
|
||||
save: async function () {
|
||||
if (!this.isDirty) return
|
||||
if (this._default_extent) this._setCenterAndZoom()
|
||||
this.backup()
|
||||
this.once('saved', () => {
|
||||
this.isDirty = false
|
||||
})
|
||||
if (this.options.editMode === 'advanced') {
|
||||
// Only save the map if the user has the rights to do so.
|
||||
this.saveSelf()
|
||||
} else {
|
||||
this.permissions.save()
|
||||
const ok = await this.saveSelf()
|
||||
if (!ok) return
|
||||
}
|
||||
await this.permissions.save()
|
||||
// Iter over all datalayers, including deleted.
|
||||
for (const datalayer of Object.values(this.datalayers)) {
|
||||
if (datalayer.isDirty) await datalayer.save()
|
||||
}
|
||||
this.isDirty = false
|
||||
this.renderEditToolbar()
|
||||
this.fire('saved')
|
||||
},
|
||||
|
||||
propagate: function () {
|
||||
let els = document.querySelectorAll('.map-name')
|
||||
for (const el of els) {
|
||||
el.textContent = this.getDisplayName()
|
||||
}
|
||||
const status = this.permissions.getShareStatusDisplay()
|
||||
els = document.querySelectorAll('.share-status')
|
||||
for (const el of els) {
|
||||
if (status) {
|
||||
el.textContent = L._('Visibility: {status}', {
|
||||
status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1831,7 +1833,7 @@ U.Map = L.Map.extend({
|
|||
|
||||
getFeatureById: function (id) {
|
||||
let feature
|
||||
for (const datalayer of Object.values(this.datalayers)) {
|
||||
for (const datalayer of this.datalayers_index) {
|
||||
feature = datalayer.getFeatureById(id)
|
||||
if (feature) return feature
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@ def test_can_change_perms_after_create(tilelayer, live_server, page):
|
|||
".datalayer-permissions select[name='edit_status'] option:checked"
|
||||
)
|
||||
expect(option).to_have_text("Inherit")
|
||||
expect(page.get_by_label("Secret edit link:")).to_be_visible()
|
||||
|
||||
|
||||
def test_alert_message_after_create(
|
||||
|
|
|
@ -63,7 +63,6 @@ def test_cancel_deleting_datalayer_should_restore(
|
|||
page.locator(".panel.right").get_by_title("Delete layer").click()
|
||||
page.get_by_role("button", name="OK").click()
|
||||
expect(markers).to_have_count(0)
|
||||
page.get_by_role("button", name="Open browser").click()
|
||||
expect(page.get_by_text("test datalayer")).to_be_hidden()
|
||||
page.get_by_role("button", name="Cancel edits").click()
|
||||
page.locator("dialog").get_by_role("button", name="OK").click()
|
||||
|
|
|
@ -40,7 +40,7 @@ def test_map_name_impacts_ui(live_server, page, tilelayer):
|
|||
|
||||
name_input.fill("something else")
|
||||
|
||||
expect(page.get_by_role("button", name="something else").nth(1)).to_be_visible()
|
||||
expect(page.get_by_role("button", name="something else").first).to_be_visible()
|
||||
|
||||
|
||||
def test_zoomcontrol_impacts_ui(live_server, page, tilelayer):
|
||||
|
|
|
@ -368,6 +368,7 @@ def test_anonymous_create(cookieclient, post_data):
|
|||
assert (
|
||||
created_map.get_anonymous_edit_url() in j["permissions"]["anonymous_edit_url"]
|
||||
)
|
||||
assert j["user"]["is_owner"] is True
|
||||
assert created_map.name == name
|
||||
key, value = created_map.signed_cookie_elements
|
||||
assert key in cookieclient.cookies
|
||||
|
|
|
@ -863,15 +863,17 @@ class MapCreate(FormLessEditMixin, PermissionsMixin, SessionMixin, CreateView):
|
|||
form.instance.owner = self.request.user
|
||||
self.object = form.save()
|
||||
permissions = self.get_permissions()
|
||||
user_data = self.get_user_data()
|
||||
# User does not have the cookie yet.
|
||||
if not self.object.owner:
|
||||
anonymous_url = self.object.get_anonymous_edit_url()
|
||||
permissions["anonymous_edit_url"] = anonymous_url
|
||||
user_data["is_owner"] = True
|
||||
response = simple_json_response(
|
||||
id=self.object.pk,
|
||||
url=self.object.get_absolute_url(),
|
||||
permissions=permissions,
|
||||
user=self.get_user_data(),
|
||||
user=user_data,
|
||||
)
|
||||
if not self.request.user.is_authenticated:
|
||||
key, value = self.object.signed_cookie_elements
|
||||
|
@ -908,7 +910,7 @@ def get_websocket_auth_token(request, map_id, map_inst):
|
|||
return simple_json_response(token=signed_token)
|
||||
|
||||
|
||||
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
||||
class MapUpdate(FormLessEditMixin, PermissionsMixin, SessionMixin, UpdateView):
|
||||
model = Map
|
||||
form_class = MapSettingsForm
|
||||
pk_url_kwarg = "map_id"
|
||||
|
@ -920,6 +922,7 @@ class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
|||
id=self.object.pk,
|
||||
url=self.object.get_absolute_url(),
|
||||
permissions=self.get_permissions(),
|
||||
user=self.get_user_data(),
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue