Merge pull request #1307 from umap-project/datalayer-editstatus
Allow to define permissions for each datalayer instead of for the whole map
This commit is contained in:
commit
ffae06aac7
36 changed files with 1168 additions and 194 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -17,4 +17,5 @@ __pycache__/
|
|||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
playwright/.auth/
|
||||
test-results/
|
||||
|
|
|
@ -53,7 +53,7 @@ dev = [
|
|||
]
|
||||
test = [
|
||||
"factory-boy==3.2.1",
|
||||
"playwright==1.37.0",
|
||||
"playwright==1.38.0",
|
||||
"pytest==6.2.5",
|
||||
"pytest-django==4.5.2",
|
||||
"pytest-playwright==0.4.2",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE=umap.tests.settings
|
||||
addopts = "--pdbcls=IPython.terminal.debugger:Pdb --no-migrations"
|
||||
addopts = --pdbcls=IPython.terminal.debugger:Pdb --no-migrations
|
||||
|
|
|
@ -26,9 +26,9 @@ def login_required_if_not_anonymous_allowed(view_func):
|
|||
return wrapper
|
||||
|
||||
|
||||
def map_permissions_check(view_func):
|
||||
def can_edit_map(view_func):
|
||||
"""
|
||||
Used for URLs dealing with the map.
|
||||
Used for URLs dealing with editing the map.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
|
|
|
@ -8,8 +8,12 @@ from django.forms.utils import ErrorList
|
|||
|
||||
from .models import Map, DataLayer
|
||||
|
||||
DEFAULT_LATITUDE = settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51
|
||||
DEFAULT_LONGITUDE = settings.LEAFLET_LONGITUDE if hasattr(settings, "LEAFLET_LONGITUDE") else 2
|
||||
DEFAULT_LATITUDE = (
|
||||
settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51
|
||||
)
|
||||
DEFAULT_LONGITUDE = (
|
||||
settings.LEAFLET_LONGITUDE if hasattr(settings, "LEAFLET_LONGITUDE") else 2
|
||||
)
|
||||
DEFAULT_CENTER = Point(DEFAULT_LONGITUDE, DEFAULT_LATITUDE)
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -21,8 +25,8 @@ class FlatErrorList(ErrorList):
|
|||
|
||||
def flat(self):
|
||||
if not self:
|
||||
return u''
|
||||
return u' — '.join([e for e in self])
|
||||
return ""
|
||||
return " — ".join([e for e in self])
|
||||
|
||||
|
||||
class SendLinkForm(forms.Form):
|
||||
|
@ -30,69 +34,79 @@ class SendLinkForm(forms.Form):
|
|||
|
||||
|
||||
class UpdateMapPermissionsForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Map
|
||||
fields = ('edit_status', 'editors', 'share_status', 'owner')
|
||||
fields = ("edit_status", "editors", "share_status", "owner")
|
||||
|
||||
|
||||
class AnonymousMapPermissionsForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AnonymousMapPermissionsForm, self).__init__(*args, **kwargs)
|
||||
help_text = _('Secret edit link is %s') % self.instance.get_anonymous_edit_url()
|
||||
self.fields['edit_status'].help_text = _(help_text)
|
||||
|
||||
STATUS = (
|
||||
(Map.ANONYMOUS, _('Everyone can edit')),
|
||||
(Map.OWNER, _('Only editable with secret edit link'))
|
||||
(Map.OWNER, _("Only editable with secret edit link")),
|
||||
(Map.ANONYMOUS, _("Everyone can edit")),
|
||||
)
|
||||
|
||||
edit_status = forms.ChoiceField(choices=STATUS)
|
||||
|
||||
class Meta:
|
||||
model = Map
|
||||
fields = ('edit_status', )
|
||||
fields = ("edit_status",)
|
||||
|
||||
|
||||
class DataLayerForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DataLayer
|
||||
fields = ("geojson", "name", "display_on_load", "rank", "settings")
|
||||
|
||||
|
||||
class DataLayerPermissionsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DataLayer
|
||||
fields = ("edit_status",)
|
||||
|
||||
|
||||
class AnonymousDataLayerPermissionsForm(forms.ModelForm):
|
||||
STATUS = (
|
||||
(DataLayer.INHERIT, _("Inherit")),
|
||||
(DataLayer.OWNER, _("Only editable with secret edit link")),
|
||||
(DataLayer.ANONYMOUS, _("Everyone can edit")),
|
||||
)
|
||||
|
||||
edit_status = forms.ChoiceField(choices=STATUS)
|
||||
|
||||
class Meta:
|
||||
model = DataLayer
|
||||
fields = ('geojson', 'name', 'display_on_load', 'rank', 'settings')
|
||||
fields = ("edit_status",)
|
||||
|
||||
|
||||
class MapSettingsForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MapSettingsForm, self).__init__(*args, **kwargs)
|
||||
self.fields['slug'].required = False
|
||||
self.fields['center'].widget.map_srid = 4326
|
||||
self.fields["slug"].required = False
|
||||
self.fields["center"].widget.map_srid = 4326
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data.get('slug', None)
|
||||
name = self.cleaned_data.get('name', None)
|
||||
slug = self.cleaned_data.get("slug", None)
|
||||
name = self.cleaned_data.get("name", None)
|
||||
if not slug and name:
|
||||
# If name is empty, don't do nothing, validation will raise
|
||||
# later on the process because name is required
|
||||
self.cleaned_data['slug'] = slugify(name) or "map"
|
||||
return self.cleaned_data['slug'][:50]
|
||||
self.cleaned_data["slug"] = slugify(name) or "map"
|
||||
return self.cleaned_data["slug"][:50]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def clean_center(self):
|
||||
if not self.cleaned_data['center']:
|
||||
if not self.cleaned_data["center"]:
|
||||
point = DEFAULT_CENTER
|
||||
self.cleaned_data['center'] = point
|
||||
return self.cleaned_data['center']
|
||||
self.cleaned_data["center"] = point
|
||||
return self.cleaned_data["center"]
|
||||
|
||||
class Meta:
|
||||
fields = ('settings', 'name', 'center', 'slug')
|
||||
fields = ("settings", "name", "center", "slug")
|
||||
model = Map
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'first_name', 'last_name')
|
||||
fields = ("username", "first_name", "last_name")
|
||||
|
|
26
umap/migrations/0013_datalayer_edit_status.py
Normal file
26
umap/migrations/0013_datalayer_edit_status.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 4.2.2 on 2023-09-19 06:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("umap", "0012_datalayer_settings"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="datalayer",
|
||||
name="edit_status",
|
||||
field=models.SmallIntegerField(
|
||||
choices=[
|
||||
(0, "Inherit"),
|
||||
(1, "Everyone"),
|
||||
(2, "Editors only"),
|
||||
(3, "Owner only"),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="edit status",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -202,7 +202,7 @@ class Map(NamedModel):
|
|||
return settings.SITE_URL + path
|
||||
|
||||
def is_anonymous_owner(self, request):
|
||||
if self.owner:
|
||||
if not request or self.owner:
|
||||
# edit cookies are only valid while map hasn't owner
|
||||
return False
|
||||
key, value = self.signed_cookie_elements
|
||||
|
@ -216,17 +216,23 @@ class Map(NamedModel):
|
|||
"""
|
||||
Define if a user can edit or not the instance, according to his account
|
||||
or the request.
|
||||
|
||||
In owner mode:
|
||||
- only owner by default (OWNER)
|
||||
- any editor if mode is EDITORS
|
||||
- anyone otherwise (ANONYMOUS)
|
||||
In anonymous owner mode:
|
||||
- only owner (has ownership cookie) by default (OWNER)
|
||||
- anyone otherwise (ANONYMOUS)
|
||||
"""
|
||||
can = False
|
||||
if request and not self.owner:
|
||||
if getattr(
|
||||
settings, "UMAP_ALLOW_ANONYMOUS", False
|
||||
) and self.is_anonymous_owner(request):
|
||||
if settings.UMAP_ALLOW_ANONYMOUS and self.is_anonymous_owner(request):
|
||||
can = True
|
||||
if self.edit_status == self.ANONYMOUS:
|
||||
can = True
|
||||
elif not user.is_authenticated:
|
||||
pass
|
||||
elif user is None:
|
||||
can = False
|
||||
elif user == self.owner:
|
||||
can = True
|
||||
elif self.edit_status == self.EDITORS and user in self.editors.all():
|
||||
|
@ -303,6 +309,17 @@ class DataLayer(NamedModel):
|
|||
Layer to store Features in.
|
||||
"""
|
||||
|
||||
INHERIT = 0
|
||||
ANONYMOUS = 1
|
||||
EDITORS = 2
|
||||
OWNER = 3
|
||||
EDIT_STATUS = (
|
||||
(INHERIT, _("Inherit")),
|
||||
(ANONYMOUS, _("Everyone")),
|
||||
(EDITORS, _("Editors only")),
|
||||
(OWNER, _("Owner only")),
|
||||
)
|
||||
|
||||
map = models.ForeignKey(Map, on_delete=models.CASCADE)
|
||||
description = models.TextField(blank=True, null=True, verbose_name=_("description"))
|
||||
geojson = models.FileField(upload_to=upload_to, blank=True, null=True)
|
||||
|
@ -315,6 +332,11 @@ class DataLayer(NamedModel):
|
|||
settings = models.JSONField(
|
||||
blank=True, null=True, verbose_name=_("settings"), default=dict
|
||||
)
|
||||
edit_status = models.SmallIntegerField(
|
||||
choices=EDIT_STATUS,
|
||||
default=INHERIT,
|
||||
verbose_name=_("edit status"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("rank",)
|
||||
|
@ -346,8 +368,7 @@ class DataLayer(NamedModel):
|
|||
path.append(str(self.map.pk))
|
||||
return os.path.join(*path)
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
def metadata(self, user=None, request=None):
|
||||
# Retrocompat: minimal settings for maps not saved after settings property
|
||||
# has been introduced
|
||||
obj = self.settings or {
|
||||
|
@ -355,6 +376,8 @@ class DataLayer(NamedModel):
|
|||
"displayOnLoad": self.display_on_load,
|
||||
}
|
||||
obj["id"] = self.pk
|
||||
obj["permissions"] = {"edit_status": self.edit_status}
|
||||
obj["editMode"] = "advanced" if self.can_edit(user, request) else 'disabled'
|
||||
return obj
|
||||
|
||||
def clone(self, map_inst=None):
|
||||
|
@ -413,6 +436,25 @@ class DataLayer(NamedModel):
|
|||
if name.startswith(f'{self.pk}_') and name.endswith(".gz"):
|
||||
self.geojson.storage.delete(os.path.join(root, name))
|
||||
|
||||
def can_edit(self, user=None, request=None):
|
||||
"""
|
||||
Define if a user can edit or not the instance, according to his account
|
||||
or the request.
|
||||
"""
|
||||
if self.edit_status == self.INHERIT:
|
||||
return self.map.can_edit(user, request)
|
||||
can = False
|
||||
if not self.map.owner:
|
||||
if settings.UMAP_ALLOW_ANONYMOUS and self.map.is_anonymous_owner(request):
|
||||
can = True
|
||||
if self.edit_status == self.ANONYMOUS:
|
||||
can = True
|
||||
elif user is not None and user == self.map.owner:
|
||||
can = True
|
||||
elif self.edit_status == self.EDITORS and user in self.map.editors.all():
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
||||
class Star(models.Model):
|
||||
at = models.DateTimeField(auto_now=True)
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
<g id="text4356-2" transform="translate(44,-124)" fill="#fff" stroke="#000" stroke-width=".1">
|
||||
<path id="path4384-2" d="m35.742 999.44 3.0762-3.0762-3.0664-3.0664 1.1914-1.1914 3.0664 3.0664 3.0566-3.0566 1.1719 1.1816-3.0469 3.0566 3.0664 3.0664-1.1914 1.1914-3.0664-3.0664-3.0762 3.0762-1.1816-1.1816" fill="#fff" stroke="#000" stroke-width=".1"/>
|
||||
</g>
|
||||
<path id="table-0" d="m78 891.36v2h12v-2zm0 3v1h4v-1zm5 0v1h7v-1zm-5 2v1h4v-1zm5 0v1h7v-1zm-5 2v1h4v-1zm5 0v1h7v-1zm-5 2v1h4v-1zm5 0v1h7v-1z" fill="#b3b3b3"/>
|
||||
<path id="path3684-2" d="m63.714 890.36-1.1428 1.1428 2.2857 2.2858 1.1428-1.1429zm-1.7143 1.7143-6.2857 6.2857 2.2857 2.2857 6.2857-6.2857zm-6.2857 6.2857-1.7143 4 4-1.7143z" fill="#b3b3b3"/>
|
||||
<path id="table-5-8-8-6" d="m30 914.86v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1z" fill="#464646"/>
|
||||
<path id="table-5-8-8-6-1" d="m54 914.86v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1zm-2 2v1h1v-1zm2 0v1h10v-1z" fill="#f2f2f2"/>
|
||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg width="144" height="144" id="svg2" version="1.1" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" sodipodi:docname="16.svg" inkscape:export-filename="/home/ybon/Code/js/Leaflet.Storage/src/img/16.png" inkscape:export-xdpi="89.996864" inkscape:export-ydpi="89.996864" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<svg width="144" height="144" id="svg2" version="1.1" inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" sodipodi:docname="16.svg" inkscape:export-filename="/home/ybon/Code/js/Leaflet.Storage/src/img/16.png" inkscape:export-xdpi="89.996864" inkscape:export-ydpi="89.996864" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs id="defs4" />
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="6.4903658" inkscape:cx="86.975067" inkscape:cy="60.628324" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="1920" inkscape:window-height="1019" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" inkscape:snap-grids="true" inkscape:snap-to-guides="true" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid type="xygrid" id="grid3004" empspacing="4" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="0" originy="0" />
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="6.4903658" inkscape:cx="86.89803" inkscape:cy="60.551287" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="1920" inkscape:window-height="1019" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" inkscape:snap-grids="true" inkscape:snap-to-guides="true" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid type="xygrid" id="grid3004" empspacing="4" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="0" originy="0" spacingy="1" spacingx="1" units="px" />
|
||||
<sodipodi:guide orientation="-1,0" position="24,144" id="guide3084" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide orientation="0,1" position="0,96" id="guide3086" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide orientation="-1,0" position="48,144" id="guide3088" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
||||
|
@ -55,7 +55,6 @@
|
|||
<g id="text4356-2" style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1" transform="translate(44,-124)">
|
||||
<path inkscape:connector-curvature="0" id="path4384-2" style="font-variant:normal;font-stretch:normal;font-size:20px;font-family:Arial;-inkscape-font-specification:Arial;fill:#ffffff;stroke:#000000;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1" d="m 35.742187,999.43835 3.076172,-3.07617 -3.066406,-3.0664 1.191406,-1.19141 3.066407,3.06641 3.05664,-3.05664 1.171875,1.18164 -3.046875,3.05664 3.066406,3.0664 -1.191406,1.19138 -3.066406,-3.06638 -3.076172,3.07618 -1.181641,-1.18165" />
|
||||
</g>
|
||||
<path style="fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 78,891.36216 v 2 h 12 v -2 z m 0,3.00002 v 1 h 4 v -1 z m 5,0 v 1 h 7 v -1 z m -5,2 v 1 h 4 v -1 z m 5,0 v 1 h 7 v -1 z m -5,2 v 1 h 4 v -1 z m 5,0 v 1 h 7 v -1 z m -5,2 v 1 h 4 v -1 z m 5,0 v 1 h 7 v -1 z" id="table-0" inkscape:connector-curvature="0" inkscape:label="table" inkscape:export-filename="/home/ybon/Code/js/leaflet-storage/src/img/browse-data.png" inkscape:export-xdpi="89.996864" inkscape:export-ydpi="89.996864" />
|
||||
<path style="fill:#b3b3b3;fill-opacity:1;stroke:none" d="m 63.71429,890.36216 -1.14285,1.1428 2.28571,2.2858 1.14285,-1.1429 z m -1.71429,1.7143 -6.285714,6.28572 2.285714,2.2857 6.28572,-6.2857 z m -6.285714,6.28572 -1.714286,4 4,-1.7143 z" id="path3684-2" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccccccc" />
|
||||
<path id="table-5-8-8-6" style="fill:#464646;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 30,914.86218 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z" inkscape:connector-curvature="0" />
|
||||
<path inkscape:connector-curvature="0" id="table-5-8-8-6-1" style="fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 54,914.86218 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z m -2,2 v 1 h 1 v -1 z m 2,0 v 1 h 10 v -1 z" />
|
||||
|
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
@ -273,7 +273,12 @@ L.U.ContinueLineAction = L.U.BaseVertexAction.extend({
|
|||
})
|
||||
|
||||
// Leaflet.Toolbar doesn't allow twice same toolbar class…
|
||||
L.U.SettingsToolbar = L.Toolbar.Control.extend({})
|
||||
L.U.SettingsToolbar = L.Toolbar.Control.extend({
|
||||
addTo: function (map) {
|
||||
if (map.options.editMode !== 'advanced') return
|
||||
L.Toolbar.Control.prototype.addTo.call(this, map)
|
||||
},
|
||||
})
|
||||
L.U.DrawToolbar = L.Toolbar.Control.extend({
|
||||
initialize: function (options) {
|
||||
L.Toolbar.Control.prototype.initialize.call(this, options)
|
||||
|
@ -608,8 +613,10 @@ L.U.DataLayer.include({
|
|||
edit.title = L._('Edit')
|
||||
table.title = L._('Edit properties in a table')
|
||||
remove.title = L._('Delete layer')
|
||||
L.DomEvent.on(toggle, 'click', this.toggle, this)
|
||||
L.DomEvent.on(zoomTo, 'click', this.zoomTo, this)
|
||||
if (this.isReadOnly()) {
|
||||
L.DomUtil.addClass(container, 'readonly')
|
||||
}
|
||||
else {
|
||||
L.DomEvent.on(edit, 'click', this.edit, this)
|
||||
L.DomEvent.on(table, 'click', this.tableEdit, this)
|
||||
L.DomEvent.on(
|
||||
|
@ -623,6 +630,9 @@ L.U.DataLayer.include({
|
|||
},
|
||||
this
|
||||
)
|
||||
}
|
||||
L.DomEvent.on(toggle, 'click', this.toggle, this)
|
||||
L.DomEvent.on(zoomTo, 'click', this.zoomTo, this)
|
||||
L.DomUtil.addClass(container, this.getHidableClass())
|
||||
L.DomUtil.classIf(container, 'off', !this.isVisible())
|
||||
container.dataset.id = L.stamp(this)
|
||||
|
@ -993,11 +1003,13 @@ L.U.Map.include({
|
|||
}
|
||||
update()
|
||||
this.once('saved', L.bind(update, this))
|
||||
logo.href = '/'
|
||||
if (this.options.editMode === 'advanced') {
|
||||
name.href = '#'
|
||||
share_status.href = '#'
|
||||
logo.href = '/'
|
||||
L.DomEvent.on(name, 'click', this.edit, this)
|
||||
L.DomEvent.on(share_status, 'click', this.permissions.edit, this.permissions)
|
||||
}
|
||||
this.on('postsync', L.bind(update, this))
|
||||
const save = L.DomUtil.create('a', 'leaflet-control-edit-save button', container)
|
||||
save.href = '#'
|
||||
|
@ -1457,7 +1469,7 @@ L.U.IframeExporter = L.Evented.extend({
|
|||
miniMap: false,
|
||||
scrollWheelZoom: false,
|
||||
zoomControl: true,
|
||||
allowEdit: false,
|
||||
editMode: 'disabled',
|
||||
moreControl: true,
|
||||
searchControl: null,
|
||||
tilelayersControl: null,
|
||||
|
|
|
@ -257,6 +257,34 @@ L.Util.hasVar = (value) => {
|
|||
return typeof value === 'string' && value.indexOf('{') != -1
|
||||
}
|
||||
|
||||
L.Util.copyToClipboard = function (textToCopy) {
|
||||
// https://stackoverflow.com/a/65996386
|
||||
// Navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
} else {
|
||||
// Use the 'out of viewport hidden text area' trick
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = textToCopy
|
||||
|
||||
// Move textarea out of the viewport so it's not visible
|
||||
textArea.style.position = 'absolute'
|
||||
textArea.style.left = '-999999px'
|
||||
|
||||
document.body.prepend(textArea)
|
||||
textArea.select()
|
||||
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
textArea.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
L.DomUtil.add = (tagName, className, container, content) => {
|
||||
const el = L.DomUtil.create(tagName, className, container)
|
||||
if (content) {
|
||||
|
|
70
umap/static/umap/js/umap.datalayer.permissions.js
Normal file
70
umap/static/umap/js/umap.datalayer.permissions.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
L.U.DataLayerPermissions = L.Class.extend({
|
||||
options: {
|
||||
edit_status: null,
|
||||
},
|
||||
|
||||
initialize: function (datalayer) {
|
||||
this.options = L.Util.setOptions(this, datalayer.options.permissions)
|
||||
this.datalayer = datalayer
|
||||
let isDirty = false
|
||||
const self = this
|
||||
try {
|
||||
Object.defineProperty(this, 'isDirty', {
|
||||
get: function () {
|
||||
return isDirty
|
||||
},
|
||||
set: function (status) {
|
||||
isDirty = status
|
||||
if (status) self.datalayer.isDirty = status
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
// Certainly IE8, which has a limited version of defineProperty
|
||||
}
|
||||
},
|
||||
|
||||
getMap: function () {
|
||||
return this.datalayer.map
|
||||
},
|
||||
|
||||
edit: function (container) {
|
||||
const fields = [
|
||||
[
|
||||
'options.edit_status',
|
||||
{
|
||||
handler: 'IntSelect',
|
||||
label: L._('Who can edit "{layer}"', { layer: this.datalayer.getName() }),
|
||||
selectOptions: this.datalayer.map.options.datalayer_edit_statuses,
|
||||
},
|
||||
],
|
||||
],
|
||||
builder = new L.U.FormBuilder(this, fields, {className: 'umap-form datalayer-permissions'}),
|
||||
form = builder.build()
|
||||
container.appendChild(form)
|
||||
},
|
||||
|
||||
getUrl: function () {
|
||||
return L.Util.template(this.datalayer.map.options.urls.datalayer_permissions, {
|
||||
map_id: this.datalayer.map.options.umap_id,
|
||||
pk: this.datalayer.umap_id,
|
||||
})
|
||||
},
|
||||
save: function () {
|
||||
if (!this.isDirty) return this.datalayer.map.continueSaving()
|
||||
const formData = new FormData()
|
||||
formData.append('edit_status', this.options.edit_status)
|
||||
this.datalayer.map.post(this.getUrl(), {
|
||||
data: formData,
|
||||
context: this,
|
||||
callback: function (data) {
|
||||
this.commit()
|
||||
this.isDirty = false
|
||||
this.datalayer.map.continueSaving()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
commit: function () {
|
||||
L.Util.extend(this.datalayer.options.permissions, this.options)
|
||||
},
|
||||
})
|
|
@ -40,7 +40,7 @@ L.U.FeatureMixin = {
|
|||
preInit: function () {},
|
||||
|
||||
isReadOnly: function () {
|
||||
return this.datalayer && this.datalayer.isRemoteLayer()
|
||||
return this.datalayer && this.datalayer.isDataReadOnly()
|
||||
},
|
||||
|
||||
getSlug: function () {
|
||||
|
|
|
@ -396,7 +396,7 @@ L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
|
|||
getOptions: function () {
|
||||
const options = []
|
||||
this.builder.map.eachDataLayerReverse((datalayer) => {
|
||||
if (datalayer.isLoaded() && !datalayer.isRemoteLayer() && datalayer.canBrowse()) {
|
||||
if (datalayer.isLoaded() && !datalayer.isDataReadOnly() && datalayer.canBrowse()) {
|
||||
options.push([L.stamp(datalayer), datalayer.getName()])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@ L.Map.mergeOptions({
|
|||
default_interactive: true,
|
||||
default_labelDirection: 'auto',
|
||||
attributionControl: false,
|
||||
allowEdit: true,
|
||||
editMode: 'advanced',
|
||||
embedControl: true,
|
||||
zoomControl: true,
|
||||
datalayersControl: true,
|
||||
|
@ -103,7 +103,7 @@ L.U.Map.include({
|
|||
L.Util.setBooleanFromQueryString(this.options, 'moreControl')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'scaleControl')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'miniMap')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'allowEdit')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'editMode')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'displayDataBrowserOnLoad')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'displayCaptionOnLoad')
|
||||
L.Util.setBooleanFromQueryString(this.options, 'captionBar')
|
||||
|
@ -122,7 +122,7 @@ L.U.Map.include({
|
|||
if (this.datalayersOnLoad)
|
||||
this.datalayersOnLoad = this.datalayersOnLoad.toString().split(',')
|
||||
|
||||
if (L.Browser.ielt9) this.options.allowEdit = false // TODO include ie9
|
||||
if (L.Browser.ielt9) this.options.editMode = 'disabled' // TODO include ie9
|
||||
|
||||
let editedFeature = null
|
||||
const self = this
|
||||
|
@ -192,16 +192,15 @@ L.U.Map.include({
|
|||
this
|
||||
)
|
||||
|
||||
let isDirty = false // global status
|
||||
let isDirty = false // self status
|
||||
try {
|
||||
Object.defineProperty(this, 'isDirty', {
|
||||
get: function () {
|
||||
return isDirty || this.dirty_datalayers.length
|
||||
return isDirty
|
||||
},
|
||||
set: function (status) {
|
||||
if (!isDirty && status) self.fire('isdirty')
|
||||
isDirty = status
|
||||
self.checkDirty()
|
||||
this.checkDirty()
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -220,7 +219,7 @@ L.U.Map.include({
|
|||
this.isDirty = true
|
||||
this._default_extent = true
|
||||
this.options.name = L._('Untitled map')
|
||||
this.options.allowEdit = true
|
||||
this.options.editMode = 'advanced'
|
||||
const datalayer = this.createDataLayer()
|
||||
datalayer.connectToMap()
|
||||
this.enableEdit()
|
||||
|
@ -238,7 +237,7 @@ L.U.Map.include({
|
|||
this.slideshow = new L.U.Slideshow(this, this.options.slideshow)
|
||||
this.permissions = new L.U.MapPermissions(this)
|
||||
this.initCaptionBar()
|
||||
if (this.options.allowEdit) {
|
||||
if (this.hasEditMode()) {
|
||||
this.editTools = new L.U.Editable(this)
|
||||
this.ui.on(
|
||||
'panel:closed panel:open',
|
||||
|
@ -277,7 +276,7 @@ L.U.Map.include({
|
|||
this.helpMenuActions = {}
|
||||
this._controls = {}
|
||||
|
||||
if (this.options.allowEdit && !this.options.noControl) {
|
||||
if (this.hasEditMode() && !this.options.noControl) {
|
||||
new L.U.EditControl(this).addTo(this)
|
||||
|
||||
new L.U.DrawToolbar({ map: this }).addTo(this)
|
||||
|
@ -496,7 +495,7 @@ L.U.Map.include({
|
|||
else this.ui.closePanel()
|
||||
}
|
||||
|
||||
if (!this.options.allowEdit) return
|
||||
if (!this.hasEditMode()) return
|
||||
|
||||
/* Edit mode only shortcuts */
|
||||
if (key === L.U.Keys.E && modifierKey && !this.editEnabled) {
|
||||
|
@ -1161,47 +1160,16 @@ L.U.Map.include({
|
|||
return JSON.stringify(umapfile, null, 2)
|
||||
},
|
||||
|
||||
save: function () {
|
||||
if (!this.isDirty) return
|
||||
if (this._default_extent) this.updateExtent()
|
||||
saveSelf: function () {
|
||||
const geojson = {
|
||||
type: 'Feature',
|
||||
geometry: this.geometry(),
|
||||
properties: this.exportOptions(),
|
||||
}
|
||||
this.backup()
|
||||
const formData = new FormData()
|
||||
formData.append('name', this.options.name)
|
||||
formData.append('center', JSON.stringify(this.geometry()))
|
||||
formData.append('settings', JSON.stringify(geojson))
|
||||
|
||||
function copyToClipboard(textToCopy) {
|
||||
// https://stackoverflow.com/a/65996386
|
||||
// Navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
} else {
|
||||
// Use the 'out of viewport hidden text area' trick
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = textToCopy
|
||||
|
||||
// Move textarea out of the viewport so it's not visible
|
||||
textArea.style.position = 'absolute'
|
||||
textArea.style.left = '-999999px'
|
||||
|
||||
document.body.prepend(textArea)
|
||||
textArea.select()
|
||||
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
textArea.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.post(this.getSaveUrl(), {
|
||||
data: formData,
|
||||
context: this,
|
||||
|
@ -1212,6 +1180,7 @@ L.U.Map.include({
|
|||
alert.content = L._('Congratulations, your map has been created!')
|
||||
this.options.umap_id = data.id
|
||||
this.permissions.setOptions(data.permissions)
|
||||
this.permissions.commit()
|
||||
if (
|
||||
data.permissions &&
|
||||
data.permissions.anonymous_edit_url &&
|
||||
|
@ -1233,7 +1202,7 @@ L.U.Map.include({
|
|||
{
|
||||
label: L._('Copy link'),
|
||||
callback: () => {
|
||||
copyToClipboard(data.permissions.anonymous_edit_url)
|
||||
L.Util.copyToClipboard(data.permissions.anonymous_edit_url)
|
||||
this.ui.alert({
|
||||
content: L._('Secret edit link copied to clipboard!'),
|
||||
level: 'info',
|
||||
|
@ -1247,22 +1216,35 @@ L.U.Map.include({
|
|||
// Do not override local changes to permissions,
|
||||
// but update in case some other editors changed them in the meantime.
|
||||
this.permissions.setOptions(data.permissions)
|
||||
this.permissions.commit()
|
||||
}
|
||||
// Update URL in case the name has changed.
|
||||
if (history && history.pushState)
|
||||
history.pushState({}, this.options.name, data.url)
|
||||
else window.location = data.url
|
||||
alert.content = data.info || alert.content
|
||||
this.once('saved', function () {
|
||||
this.isDirty = false
|
||||
this.ui.alert(alert)
|
||||
})
|
||||
this.once('saved', () => this.ui.alert(alert))
|
||||
this.ui.closePanel()
|
||||
this.permissions.save()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
save: function () {
|
||||
if (!this.isDirty) return
|
||||
if (this._default_extent) this.updateExtent()
|
||||
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()
|
||||
}
|
||||
},
|
||||
|
||||
sendEditLink: function () {
|
||||
const url = L.Util.template(this.options.urls.map_send_edit_link, {
|
||||
map_id: this.options.umap_id,
|
||||
|
@ -1330,14 +1312,14 @@ L.U.Map.include({
|
|||
datalayer = this.lastUsedDataLayer
|
||||
if (
|
||||
datalayer &&
|
||||
!datalayer.isRemoteLayer() &&
|
||||
!datalayer.isDataReadOnly() &&
|
||||
datalayer.canBrowse() &&
|
||||
datalayer.isVisible()
|
||||
) {
|
||||
return datalayer
|
||||
}
|
||||
datalayer = this.findDataLayer((datalayer) => {
|
||||
if (!datalayer.isRemoteLayer() && datalayer.canBrowse()) {
|
||||
if (!datalayer.isDataReadOnly() && datalayer.canBrowse()) {
|
||||
fallback = datalayer
|
||||
if (datalayer.isVisible()) return true
|
||||
}
|
||||
|
@ -1733,20 +1715,28 @@ L.U.Map.include({
|
|||
_advancedActions: function (container) {
|
||||
const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
|
||||
const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
|
||||
if (this.permissions.isOwner()) {
|
||||
const del = L.DomUtil.create('a', 'button umap-delete', advancedButtons)
|
||||
del.href = '#'
|
||||
del.title = L._('Delete map')
|
||||
del.textContent = L._('Delete')
|
||||
L.DomEvent.on(del, 'click', L.DomEvent.stop).on(del, 'click', this.del, this)
|
||||
const empty = L.DomUtil.create('a', 'button umap-empty', advancedButtons)
|
||||
empty.href = '#'
|
||||
empty.textContent = L._('Empty')
|
||||
empty.title = L._('Delete all layers')
|
||||
L.DomEvent.on(empty, 'click', L.DomEvent.stop).on(
|
||||
empty,
|
||||
'click',
|
||||
this.empty,
|
||||
this
|
||||
)
|
||||
}
|
||||
const clone = L.DomUtil.create('a', 'button umap-clone', advancedButtons)
|
||||
clone.href = '#'
|
||||
clone.textContent = L._('Clone')
|
||||
clone.title = L._('Clone this map')
|
||||
L.DomEvent.on(clone, 'click', L.DomEvent.stop).on(clone, 'click', this.clone, this)
|
||||
const empty = L.DomUtil.create('a', 'button umap-empty', advancedButtons)
|
||||
empty.href = '#'
|
||||
empty.textContent = L._('Empty')
|
||||
empty.title = L._('Delete all layers')
|
||||
L.DomEvent.on(empty, 'click', L.DomEvent.stop).on(empty, 'click', this.empty, this)
|
||||
const download = L.DomUtil.create('a', 'button umap-download', advancedButtons)
|
||||
download.href = '#'
|
||||
download.textContent = L._('Download')
|
||||
|
@ -1761,6 +1751,7 @@ L.U.Map.include({
|
|||
|
||||
edit: function () {
|
||||
if (!this.editEnabled) return
|
||||
if (this.options.editMode !== 'advanced') return
|
||||
const container = L.DomUtil.create('div', 'umap-edit-container'),
|
||||
metadataFields = ['options.name', 'options.description'],
|
||||
title = L.DomUtil.create('h3', '', container)
|
||||
|
@ -1796,6 +1787,10 @@ L.U.Map.include({
|
|||
this.fire('edit:disabled')
|
||||
},
|
||||
|
||||
hasEditMode: function () {
|
||||
return this.options.editMode === 'simple' || this.options.editMode === 'advanced'
|
||||
},
|
||||
|
||||
getDisplayName: function () {
|
||||
return this.options.name || L._('Untitled map')
|
||||
},
|
||||
|
@ -1952,7 +1947,7 @@ L.U.Map.include({
|
|||
items = items.concat(e.relatedTarget.getContextMenuItems(e))
|
||||
}
|
||||
}
|
||||
if (this.options.allowEdit) {
|
||||
if (this.hasEditMode()) {
|
||||
items.push('-')
|
||||
if (this.editEnabled) {
|
||||
if (!this.isDirty) {
|
||||
|
|
|
@ -193,6 +193,7 @@ L.U.DataLayer = L.Evented.extend({
|
|||
options: {
|
||||
displayOnLoad: true,
|
||||
browsable: true,
|
||||
editMode: 'advanced',
|
||||
},
|
||||
|
||||
initialize: function (map, data) {
|
||||
|
@ -261,6 +262,7 @@ L.U.DataLayer = L.Evented.extend({
|
|||
}
|
||||
this.backupOptions()
|
||||
this.connectToMap()
|
||||
this.permissions = new L.U.DataLayerPermissions(this)
|
||||
if (this.showAtLoad()) this.show()
|
||||
if (!this.umap_id) this.isDirty = true
|
||||
|
||||
|
@ -350,6 +352,12 @@ L.U.DataLayer = L.Evented.extend({
|
|||
this.map.get(this._dataUrl(), {
|
||||
callback: function (geojson, response) {
|
||||
this._last_modified = response.getResponseHeader('Last-Modified')
|
||||
// FIXME: for now this property is set dynamically from backend
|
||||
// And thus it's not in the geojson file in the server
|
||||
// So do not let all options to be reset
|
||||
// Fix is a proper migration so all datalayers settings are
|
||||
// in DB, and we remove it from geojson flat files.
|
||||
geojson['_umap_options']['editMode'] = this.options.editMode
|
||||
this.fromUmapGeoJSON(geojson)
|
||||
this.backupOptions()
|
||||
this.fire('loaded')
|
||||
|
@ -489,7 +497,7 @@ L.U.DataLayer = L.Evented.extend({
|
|||
})
|
||||
|
||||
// No browser cache for owners/editors.
|
||||
if (this.map.options.allowEdit) url = `${url}?${Date.now()}`
|
||||
if (this.map.hasEditMode()) url = `${url}?${Date.now()}`
|
||||
return url
|
||||
},
|
||||
|
||||
|
@ -1182,18 +1190,20 @@ L.U.DataLayer = L.Evented.extend({
|
|||
}
|
||||
},
|
||||
|
||||
metadata: function () {
|
||||
return {
|
||||
id: this.umap_id,
|
||||
name: this.options.name,
|
||||
displayOnLoad: this.options.displayOnLoad,
|
||||
}
|
||||
},
|
||||
|
||||
getRank: function () {
|
||||
return this.map.datalayers_index.indexOf(this)
|
||||
},
|
||||
|
||||
isReadOnly: function () {
|
||||
// isReadOnly must return true if unset
|
||||
return this.options.editMode === 'disabled'
|
||||
},
|
||||
|
||||
isDataReadOnly: function () {
|
||||
// This layer cannot accept features
|
||||
return this.isReadOnly() || this.isRemoteLayer()
|
||||
},
|
||||
|
||||
save: function () {
|
||||
if (this.isDeleted) return this.saveDelete()
|
||||
if (!this.isLoaded()) {
|
||||
|
@ -1220,7 +1230,7 @@ L.U.DataLayer = L.Evented.extend({
|
|||
this._loaded = true
|
||||
this.redraw() // Needed for reordering features
|
||||
this.isDirty = false
|
||||
this.map.continueSaving()
|
||||
this.permissions.save()
|
||||
},
|
||||
context: this,
|
||||
headers: this._last_modified
|
||||
|
|
|
@ -20,7 +20,9 @@ L.U.MapPermissions = L.Class.extend({
|
|||
},
|
||||
set: function (status) {
|
||||
isDirty = status
|
||||
if (status) self.map.isDirty = status
|
||||
if (status) {
|
||||
self.map.isDirty = status
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -35,13 +37,13 @@ L.U.MapPermissions = L.Class.extend({
|
|||
isOwner: function () {
|
||||
return (
|
||||
this.map.options.user &&
|
||||
this.map.permissions.options.owner &&
|
||||
this.map.options.user.id == this.map.permissions.options.owner.id
|
||||
this.map.options.permissions.owner &&
|
||||
this.map.options.user.id == this.map.options.permissions.owner.id
|
||||
)
|
||||
},
|
||||
|
||||
isAnonymousMap: function () {
|
||||
return !this.map.permissions.options.owner
|
||||
return !this.map.options.permissions.owner
|
||||
},
|
||||
|
||||
getMap: function () {
|
||||
|
@ -49,6 +51,7 @@ L.U.MapPermissions = L.Class.extend({
|
|||
},
|
||||
|
||||
edit: function () {
|
||||
if (this.map.options.editMode !== 'advanced') return
|
||||
if (!this.map.options.umap_id)
|
||||
return this.map.ui.alert({
|
||||
content: L._('Please save the map first'),
|
||||
|
@ -59,15 +62,16 @@ L.U.MapPermissions = L.Class.extend({
|
|||
title = L.DomUtil.create('h4', '', container)
|
||||
if (this.isAnonymousMap()) {
|
||||
if (this.options.anonymous_edit_url) {
|
||||
const helpText = L._('Secret edit link is:<br>{link}', {
|
||||
link: this.options.anonymous_edit_url,
|
||||
})
|
||||
const helpText = `${L._('Secret edit link:')}<br>${
|
||||
this.options.anonymous_edit_url
|
||||
}`
|
||||
L.DomUtil.add('p', 'help-text', container, helpText)
|
||||
fields.push([
|
||||
'options.edit_status',
|
||||
{
|
||||
handler: 'IntSelect',
|
||||
label: L._('Who can edit'),
|
||||
selectOptions: this.map.options.anonymous_edit_statuses,
|
||||
selectOptions: this.map.options.edit_statuses,
|
||||
helpText: helpText,
|
||||
},
|
||||
])
|
||||
|
@ -122,6 +126,10 @@ L.U.MapPermissions = L.Class.extend({
|
|||
this
|
||||
)
|
||||
}
|
||||
L.DomUtil.add('h3', '', container, L._('Datalayers'))
|
||||
this.map.eachDataLayer((datalayer) => {
|
||||
datalayer.permissions.edit(container)
|
||||
})
|
||||
this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
|
||||
},
|
||||
|
||||
|
@ -197,6 +205,8 @@ L.U.MapPermissions = L.Class.extend({
|
|||
},
|
||||
|
||||
getShareStatusDisplay: function () {
|
||||
return Object.fromEntries(this.map.options.share_statuses)[this.options.share_status]
|
||||
}
|
||||
return Object.fromEntries(this.map.options.share_statuses)[
|
||||
this.options.share_status
|
||||
]
|
||||
},
|
||||
})
|
||||
|
|
|
@ -756,15 +756,18 @@ a.map-name:after {
|
|||
.umap-toggle-edit {
|
||||
background-position: -44px -48px;
|
||||
}
|
||||
.readonly .layer-table-edit,
|
||||
.off .layer-table-edit {
|
||||
background-position: -74px -1px;
|
||||
}
|
||||
.readonly .layer-edit,
|
||||
.off .layer-edit {
|
||||
background-position: -51px -72px;
|
||||
}
|
||||
.off .layer-zoom_to {
|
||||
background-position: -25px -54px;
|
||||
}
|
||||
.readonly .layer-delete,
|
||||
.off .layer-delete {
|
||||
background-position: -122px -121px;
|
||||
}
|
||||
|
|
|
@ -207,6 +207,7 @@ describe('L.U.Map.Export', function () {
|
|||
_umap_options: {
|
||||
displayOnLoad: true,
|
||||
browsable: true,
|
||||
editMode: 'advanced',
|
||||
iconClass: 'Default',
|
||||
name: 'Elephants',
|
||||
id: 62,
|
||||
|
|
|
@ -105,7 +105,7 @@ describe('L.U.Map', function () {
|
|||
window.confirm = oldConfirm
|
||||
})
|
||||
|
||||
it('should ask for confirmation on delete link click', function (done) {
|
||||
it('should ask for confirmation on delete link click', function () {
|
||||
var button = qs('a.update-map-settings')
|
||||
assert.ok(button, 'update map info button exists')
|
||||
happen.click(button)
|
||||
|
@ -117,7 +117,7 @@ describe('L.U.Map', function () {
|
|||
this.server.respond()
|
||||
assert(window.confirm.calledOnce)
|
||||
window.confirm.restore()
|
||||
done()
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -34,11 +34,11 @@ describe('L.Permissions', function () {
|
|||
describe('#anonymous with cookie', function () {
|
||||
var button
|
||||
|
||||
it('should only allow edit_status', function () {
|
||||
it('should not allow share_status nor owner', function () {
|
||||
this.map.permissions.options.anonymous_edit_url = 'http://anonymous.url'
|
||||
delete this.map.permissions.options.owner
|
||||
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
|
||||
})
|
||||
|
@ -49,9 +49,10 @@ describe('L.Permissions', function () {
|
|||
|
||||
it('should only allow editors', function () {
|
||||
this.map.permissions.options.owner = { id: 1, url: '/url', name: 'jojo' }
|
||||
delete this.map.permissions.options.anonymous_edit_url
|
||||
delete this.map.options.user
|
||||
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
|
||||
|
@ -66,8 +67,6 @@ describe('L.Permissions', function () {
|
|||
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
|
||||
})
|
||||
|
|
|
@ -190,7 +190,7 @@ function initMap(options) {
|
|||
name: 'name of the map',
|
||||
description: 'The description of the map',
|
||||
locale: 'en',
|
||||
allowEdit: true,
|
||||
editMode: 'advanced',
|
||||
moreControl: true,
|
||||
scaleControl: true,
|
||||
miniMap: false,
|
||||
|
@ -198,6 +198,20 @@ function initMap(options) {
|
|||
displayCaptionOnLoad: false,
|
||||
displayPopupFooter: false,
|
||||
displayDataBrowserOnLoad: false,
|
||||
permissions: {
|
||||
share_status: 1,
|
||||
owner: {
|
||||
id: 1,
|
||||
name: 'ybon',
|
||||
url: '/en/user/ybon/',
|
||||
},
|
||||
editors: [],
|
||||
},
|
||||
user: {
|
||||
id: 1,
|
||||
name: 'foofoo',
|
||||
url: '/en/me',
|
||||
},
|
||||
},
|
||||
}
|
||||
default_options.properties.datalayers.push(defaultDatalayerData())
|
||||
|
@ -319,7 +333,11 @@ var RESPONSES = {
|
|||
datalayer64_GET: {
|
||||
crs: null,
|
||||
type: 'FeatureCollection',
|
||||
_umap_options: defaultDatalayerData({name: 'hidden', id: 64, displayOnLoad: false }),
|
||||
_umap_options: defaultDatalayerData({
|
||||
name: 'hidden',
|
||||
id: 64,
|
||||
displayOnLoad: false,
|
||||
}),
|
||||
features: [
|
||||
{
|
||||
geometry: {
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<script src="../js/umap.slideshow.js"></script>
|
||||
<script src="../js/umap.tableeditor.js"></script>
|
||||
<script src="../js/umap.permissions.js"></script>
|
||||
<script src="../js/umap.datalayer.permissions.js"></script>
|
||||
<script src="../js/umap.js"></script>
|
||||
<script src="../js/umap.ui.js"></script>
|
||||
<link rel="stylesheet" href="../vendors/leaflet/leaflet.css" />
|
||||
|
|
|
@ -34,11 +34,12 @@
|
|||
<script src="{{ STATIC_URL }}umap/js/umap.forms.js"></script>
|
||||
<script src="{{ STATIC_URL }}umap/js/umap.icon.js"></script>
|
||||
<script src="{{ STATIC_URL }}umap/js/umap.features.js"></script>
|
||||
<script src="{{ STATIC_URL }}umap/js/umap.permissions.js"></script>
|
||||
<script src="{{ STATIC_URL }}umap/js/umap.datalayer.permissions.js"></script>
|
||||
<script src="{{ STATIC_URL }}umap/js/umap.layer.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.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.ui.js"></script>
|
||||
{% endcompress %}
|
||||
|
|
|
@ -28,7 +28,7 @@ def umap_js(locale=None):
|
|||
@register.inclusion_tag('umap/map_fragment.html')
|
||||
def map_fragment(map_instance, **kwargs):
|
||||
layers = DataLayer.objects.filter(map=map_instance)
|
||||
datalayer_data = [c.metadata for c in layers]
|
||||
datalayer_data = [c.metadata() for c in layers]
|
||||
map_settings = map_instance.settings
|
||||
if "properties" not in map_settings:
|
||||
map_settings['properties'] = {}
|
||||
|
@ -37,7 +37,7 @@ def map_fragment(map_instance, **kwargs):
|
|||
'datalayers': datalayer_data,
|
||||
'urls': _urls_for_js(),
|
||||
'STATIC_URL': settings.STATIC_URL,
|
||||
"allowEdit": False,
|
||||
"editMode": 'disabled',
|
||||
'hash': False,
|
||||
'attributionControl': False,
|
||||
'scrollWheelZoom': False,
|
||||
|
|
|
@ -2,6 +2,7 @@ import json
|
|||
|
||||
import factory
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.base import ContentFile
|
||||
from django.urls import reverse
|
||||
|
||||
from umap.forms import DEFAULT_CENTER
|
||||
|
@ -9,6 +10,25 @@ from umap.models import DataLayer, Licence, Map, TileLayer
|
|||
|
||||
User = get_user_model()
|
||||
|
||||
DATALAYER_DATA = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [13.68896484375, 48.55297816440071],
|
||||
},
|
||||
"properties": {
|
||||
"_umap_options": {"color": "DarkCyan", "iconClass": "Ball"},
|
||||
"name": "Here",
|
||||
"description": "Da place anonymous again 755",
|
||||
},
|
||||
}
|
||||
],
|
||||
"_umap_options": {"displayOnLoad": True, "name": "Donau", "id": 926},
|
||||
}
|
||||
|
||||
|
||||
class LicenceFactory(factory.django.DjangoModelFactory):
|
||||
name = "WTFPL"
|
||||
|
@ -82,10 +102,18 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
|
|||
name = "test datalayer"
|
||||
description = "test description"
|
||||
display_on_load = True
|
||||
settings = {"displayOnLoad": True, "browsable": True, name: "test datalayer"}
|
||||
geojson = factory.django.FileField(
|
||||
data="""{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[13.68896484375,48.55297816440071]},"properties":{"_umap_options":{"color":"DarkCyan","iconClass":"Ball"},"name":"Here","description":"Da place anonymous again 755"}}],"_umap_options":{"displayOnLoad":true,"name":"Donau","id":926}}"""
|
||||
) # noqa
|
||||
settings = {"displayOnLoad": True, "browsable": True, "name": name}
|
||||
geojson = factory.django.FileField()
|
||||
|
||||
@factory.post_generation
|
||||
def geojson_data(obj, create, extracted, **kwargs):
|
||||
# Make sure DB settings and file settings are aligned.
|
||||
# At some point, file settings should be removed, but we are not there yet.
|
||||
data = DATALAYER_DATA.copy()
|
||||
obj.settings["name"] = obj.name
|
||||
data["_umap_options"] = obj.settings
|
||||
with open(obj.geojson.path, mode="w") as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
class Meta:
|
||||
model = DataLayer
|
||||
|
|
|
@ -74,7 +74,7 @@ def allow_anonymous(settings):
|
|||
|
||||
@pytest.fixture
|
||||
def datalayer(map):
|
||||
return DataLayerFactory(map=map, name="Default Datalayer")
|
||||
return DataLayerFactory(map=map)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
149
umap/tests/integration/test_anonymous_owned_map.py
Normal file
149
umap/tests/integration/test_anonymous_owned_map.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
import re
|
||||
from time import sleep
|
||||
|
||||
import pytest
|
||||
from django.core.signing import get_cookie_signer
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from umap.models import DataLayer
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
||||
pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures("allow_anonymous")]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def owner_session(anonymap, context, live_server):
|
||||
key, value = anonymap.signed_cookie_elements
|
||||
signed = get_cookie_signer(salt=key).sign(value)
|
||||
context.add_cookies([{"name": key, "value": signed, "url": live_server.url}])
|
||||
return context.new_page()
|
||||
|
||||
|
||||
def test_map_load_with_owner(anonymap, live_server, owner_session):
|
||||
owner_session.goto(f"{live_server.url}{anonymap.get_absolute_url()}")
|
||||
map_el = owner_session.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = owner_session.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_visible()
|
||||
enable.click()
|
||||
disable = owner_session.get_by_role("link", name="Disable editing")
|
||||
expect(disable).to_be_visible()
|
||||
save = owner_session.get_by_title("Save current edits (Ctrl+S)")
|
||||
expect(save).to_be_visible()
|
||||
add_marker = owner_session.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
edit_settings = owner_session.get_by_title("Edit map settings")
|
||||
expect(edit_settings).to_be_visible()
|
||||
edit_permissions = owner_session.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
|
||||
|
||||
def test_map_load_with_anonymous(anonymap, live_server, page):
|
||||
page.goto(f"{live_server.url}{anonymap.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_hidden()
|
||||
|
||||
|
||||
def test_map_load_with_anonymous_but_editable_layer(
|
||||
anonymap, live_server, page, datalayer
|
||||
):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{anonymap.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_visible()
|
||||
enable.click()
|
||||
disable = page.get_by_role("link", name="Disable editing")
|
||||
expect(disable).to_be_visible()
|
||||
save = page.get_by_title("Save current edits (Ctrl+S)")
|
||||
expect(save).to_be_visible()
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
edit_settings = page.get_by_title("Edit map settings")
|
||||
expect(edit_settings).to_be_hidden()
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_hidden()
|
||||
|
||||
|
||||
def test_owner_permissions_form(map, datalayer, live_server, owner_session):
|
||||
owner_session.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
edit_permissions = owner_session.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
edit_permissions.click()
|
||||
select = owner_session.locator(".umap-field-share_status select")
|
||||
expect(select).to_be_hidden()
|
||||
owner_field = owner_session.locator(".umap-field-owner")
|
||||
expect(owner_field).to_be_hidden()
|
||||
editors_field = owner_session.locator(".umap-field-editors input")
|
||||
expect(editors_field).to_be_hidden()
|
||||
datalayer_label = owner_session.get_by_text('Who can edit "test datalayer"')
|
||||
expect(datalayer_label).to_be_visible()
|
||||
options = owner_session.locator(
|
||||
".datalayer-permissions select[name='edit_status'] option"
|
||||
)
|
||||
expect(options).to_have_count(3)
|
||||
option = owner_session.locator(
|
||||
".datalayer-permissions select[name='edit_status'] option:checked"
|
||||
)
|
||||
expect(option).to_have_text("Inherit")
|
||||
|
||||
|
||||
def test_anonymous_can_add_marker_on_editable_layer(
|
||||
anonymap, datalayer, live_server, page
|
||||
):
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.name = "Should not be in the select"
|
||||
datalayer.save() # Non editable by anonymous users
|
||||
assert datalayer.map == anonymap
|
||||
other = DataLayerFactory(
|
||||
map=anonymap, edit_status=DataLayer.ANONYMOUS, name="Editable"
|
||||
)
|
||||
assert other.map == anonymap
|
||||
page.goto(f"{live_server.url}{anonymap.get_absolute_url()}?edit")
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
marker = page.locator(".leaflet-marker-icon")
|
||||
map_el = page.locator("#map")
|
||||
expect(marker).to_have_count(2)
|
||||
expect(map_el).not_to_have_class(re.compile("umap-ui"))
|
||||
add_marker.click()
|
||||
map_el.click(position={"x": 100, "y": 100})
|
||||
expect(marker).to_have_count(3)
|
||||
# Edit panel is open
|
||||
expect(map_el).to_have_class(re.compile("umap-ui"))
|
||||
datalayer_select = page.locator("select[name='datalayer']")
|
||||
expect(datalayer_select).to_be_visible()
|
||||
options = page.locator("select[name='datalayer'] option")
|
||||
expect(options).to_have_count(1) # Only Editable layer should be listed
|
||||
option = page.locator("select[name='datalayer'] option:checked")
|
||||
expect(option).to_have_text(other.name)
|
||||
|
||||
|
||||
def test_can_change_perms_after_create(tilelayer, live_server, page):
|
||||
page.goto(f"{live_server.url}/en/map/new")
|
||||
save = page.get_by_title("Save current edits")
|
||||
expect(save).to_be_visible()
|
||||
save.click()
|
||||
sleep(1) # Let save ajax go back
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
edit_permissions.click()
|
||||
select = page.locator(".umap-field-share_status select")
|
||||
expect(select).to_be_hidden()
|
||||
owner_field = page.locator(".umap-field-owner")
|
||||
expect(owner_field).to_be_hidden()
|
||||
editors_field = page.locator(".umap-field-editors input")
|
||||
expect(editors_field).to_be_hidden()
|
||||
datalayer_label = page.get_by_text('Who can edit "Layer 1"')
|
||||
expect(datalayer_label).to_be_visible()
|
||||
options = page.locator(".datalayer-permissions select[name='edit_status'] option")
|
||||
expect(options).to_have_count(3)
|
||||
option = page.locator(
|
||||
".datalayer-permissions select[name='edit_status'] option:checked"
|
||||
)
|
||||
expect(option).to_have_text("Inherit")
|
37
umap/tests/integration/test_map.py
Normal file
37
umap/tests/integration/test_map.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from umap.models import Map
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_remote_layer_should_not_be_used_as_datalayer_for_created_features(
|
||||
map, live_server, datalayer, page
|
||||
):
|
||||
# Faster than doing a login
|
||||
map.edit_status = Map.ANONYMOUS
|
||||
map.save()
|
||||
datalayer.settings["remoteData"] = {
|
||||
"url": "https://overpass-api.de/api/interpreter?data=[out:xml];node[harbour=yes]({south},{west},{north},{east});out body;",
|
||||
"format": "osm",
|
||||
"from": "10",
|
||||
}
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
toggle = page.get_by_title("See data layers")
|
||||
expect(toggle).to_be_visible()
|
||||
toggle.click()
|
||||
layers = page.locator(".umap-browse-datalayers li")
|
||||
expect(layers).to_have_count(1)
|
||||
map_el = page.locator("#map")
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
marker = page.locator(".leaflet-marker-icon")
|
||||
expect(marker).to_have_count(0)
|
||||
add_marker.click()
|
||||
map_el.click(position={"x": 100, "y": 100})
|
||||
expect(marker).to_have_count(1)
|
||||
# A new datalayer has been created to host this created feature
|
||||
# given the remote one cannot accept new features
|
||||
expect(layers).to_have_count(2)
|
214
umap/tests/integration/test_owned_map.py
Normal file
214
umap/tests/integration/test_owned_map.py
Normal file
|
@ -0,0 +1,214 @@
|
|||
from time import sleep
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from umap.models import DataLayer, Map
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def login(context, settings, live_server):
|
||||
def do_login(user):
|
||||
# TODO use storage state to do login only once per session
|
||||
# https://playwright.dev/python/docs/auth
|
||||
settings.ENABLE_ACCOUNT_LOGIN = True
|
||||
page = context.new_page()
|
||||
page.goto(f"{live_server.url}/en/")
|
||||
page.locator(".login").click()
|
||||
page.get_by_placeholder("Username").fill(user.username)
|
||||
page.get_by_placeholder("Password").fill("123123")
|
||||
page.locator('#login_form input[type="submit"]').click()
|
||||
sleep(1) # Time for ajax login POST to proceed
|
||||
return page
|
||||
|
||||
return do_login
|
||||
|
||||
|
||||
def test_map_update_with_owner(map, live_server, login):
|
||||
page = login(map.owner)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_visible()
|
||||
enable.click()
|
||||
disable = page.get_by_role("link", name="Disable editing")
|
||||
expect(disable).to_be_visible()
|
||||
save = page.get_by_title("Save current edits (Ctrl+S)")
|
||||
expect(save).to_be_visible()
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
edit_settings = page.get_by_title("Edit map settings")
|
||||
expect(edit_settings).to_be_visible()
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
|
||||
|
||||
def test_map_update_with_anonymous(map, live_server, page):
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_hidden()
|
||||
|
||||
|
||||
def test_map_update_with_anonymous_but_editable_datalayer(
|
||||
map, datalayer, live_server, page
|
||||
):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_visible()
|
||||
enable.click()
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
edit_settings = page.get_by_title("Edit map settings")
|
||||
expect(edit_settings).to_be_hidden()
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_hidden()
|
||||
|
||||
|
||||
def test_owner_permissions_form(map, datalayer, live_server, login):
|
||||
page = login(map.owner)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
edit_permissions.click()
|
||||
select = page.locator(".umap-field-share_status select")
|
||||
expect(select).to_be_visible()
|
||||
# expect(select).to_have_value(Map.PUBLIC) # Does not work
|
||||
owner_field = page.locator(".umap-field-owner")
|
||||
expect(owner_field).to_be_visible()
|
||||
editors_field = page.locator(".umap-field-editors input")
|
||||
expect(editors_field).to_be_visible()
|
||||
datalayer_label = page.get_by_text('Who can edit "test datalayer"')
|
||||
expect(datalayer_label).to_be_visible()
|
||||
options = page.locator(".datalayer-permissions select[name='edit_status'] option")
|
||||
expect(options).to_have_count(4)
|
||||
option = page.locator(
|
||||
".datalayer-permissions select[name='edit_status'] option:checked"
|
||||
)
|
||||
expect(option).to_have_text("Inherit")
|
||||
|
||||
|
||||
def test_map_update_with_editor(map, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
map_el = page.locator("#map")
|
||||
expect(map_el).to_be_visible()
|
||||
enable = page.get_by_role("link", name="Edit")
|
||||
expect(enable).to_be_visible()
|
||||
enable.click()
|
||||
disable = page.get_by_role("link", name="Disable editing")
|
||||
expect(disable).to_be_visible()
|
||||
save = page.get_by_title("Save current edits (Ctrl+S)")
|
||||
expect(save).to_be_visible()
|
||||
add_marker = page.get_by_title("Draw a marker")
|
||||
expect(add_marker).to_be_visible()
|
||||
edit_settings = page.get_by_title("Edit map settings")
|
||||
expect(edit_settings).to_be_visible()
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
|
||||
|
||||
def test_permissions_form_with_editor(map, datalayer, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
edit_permissions.click()
|
||||
select = page.locator(".umap-field-share_status select")
|
||||
expect(select).to_be_hidden()
|
||||
# expect(select).to_have_value(Map.PUBLIC) # Does not work
|
||||
owner_field = page.locator(".umap-field-owner")
|
||||
expect(owner_field).to_be_hidden()
|
||||
editors_field = page.locator(".umap-field-editors input")
|
||||
expect(editors_field).to_be_visible()
|
||||
datalayer_label = page.get_by_text('Who can edit "test datalayer"')
|
||||
expect(datalayer_label).to_be_visible()
|
||||
|
||||
|
||||
def test_owner_has_delete_map_button(map, live_server, login):
|
||||
page = login(map.owner)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
settings = page.get_by_title("Edit map settings")
|
||||
expect(settings).to_be_visible()
|
||||
settings.click()
|
||||
advanced = page.get_by_text("Advanced actions")
|
||||
expect(advanced).to_be_visible()
|
||||
advanced.click()
|
||||
delete = page.get_by_role("link", name="Delete")
|
||||
expect(delete).to_be_visible()
|
||||
|
||||
|
||||
def test_editor_do_not_have_delete_map_button(map, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
settings = page.get_by_title("Edit map settings")
|
||||
expect(settings).to_be_visible()
|
||||
settings.click()
|
||||
advanced = page.get_by_text("Advanced actions")
|
||||
expect(advanced).to_be_visible()
|
||||
advanced.click()
|
||||
delete = page.get_by_role("link", name="Delete")
|
||||
expect(delete).to_be_hidden()
|
||||
|
||||
|
||||
def test_can_change_perms_after_create(tilelayer, live_server, login, user):
|
||||
page = login(user)
|
||||
page.goto(f"{live_server.url}/en/map/new")
|
||||
save = page.get_by_title("Save current edits")
|
||||
expect(save).to_be_visible()
|
||||
save.click()
|
||||
sleep(1) # Let save ajax go back
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
expect(edit_permissions).to_be_visible()
|
||||
edit_permissions.click()
|
||||
select = page.locator(".umap-field-share_status select")
|
||||
expect(select).to_be_visible()
|
||||
option = page.locator("select[name='share_status'] option:checked")
|
||||
expect(option).to_have_text("Everyone (public)")
|
||||
owner_field = page.locator(".umap-field-owner")
|
||||
expect(owner_field).to_be_visible()
|
||||
editors_field = page.locator(".umap-field-editors input")
|
||||
expect(editors_field).to_be_visible()
|
||||
datalayer_label = page.get_by_text('Who can edit "Layer 1"')
|
||||
expect(datalayer_label).to_be_visible()
|
||||
options = page.locator(".datalayer-permissions select[name='edit_status'] option")
|
||||
expect(options).to_have_count(4)
|
||||
option = page.locator(
|
||||
".datalayer-permissions select[name='edit_status'] option:checked"
|
||||
)
|
||||
expect(option).to_have_text("Inherit")
|
||||
|
||||
|
||||
def test_can_change_owner(map, live_server, login, user):
|
||||
page = login(map.owner)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
edit_permissions.click()
|
||||
close = page.locator(".umap-field-owner .close")
|
||||
close.click()
|
||||
input = page.locator("input.edit-owner")
|
||||
input.type(user.username)
|
||||
input.press("Tab")
|
||||
save = page.get_by_title("Save current edits")
|
||||
expect(save).to_be_visible()
|
||||
save.click()
|
||||
sleep(1) # Let save ajax go
|
||||
modified = Map.objects.get(pk=map.pk)
|
||||
assert modified.owner == user
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
from django.core.files.base import ContentFile
|
||||
|
||||
from .base import DataLayerFactory, MapFactory
|
||||
from umap.models import DataLayer, Map
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -21,7 +22,7 @@ def test_datalayers_should_be_ordered_by_rank(map, datalayer):
|
|||
def test_upload_to(map, datalayer):
|
||||
map.pk = 302
|
||||
datalayer.pk = 17
|
||||
assert datalayer.upload_to().startswith('datalayer/2/0/302/17_')
|
||||
assert datalayer.upload_to().startswith("datalayer/2/0/302/17_")
|
||||
|
||||
|
||||
def test_save_should_use_pk_as_name(map, datalayer):
|
||||
|
@ -81,3 +82,120 @@ def test_should_remove_old_versions_on_save(datalayer, map, settings):
|
|||
assert os.path.basename(other) in files
|
||||
assert os.path.basename(other + ".gz") in files
|
||||
assert os.path.basename(older) not in files
|
||||
assert os.path.basename(older + ".gz") not in files
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_editors_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
||||
def test_owner_can_edit_in_editors_mode(datalayer, user):
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(datalayer.map.owner)
|
||||
|
||||
|
||||
def test_editor_can_edit_in_editors_mode(datalayer, user):
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_anonymous_can_edit_in_public_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit()
|
||||
|
||||
|
||||
def test_owner_can_edit_in_public_mode(datalayer, user):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(datalayer.map.owner)
|
||||
|
||||
|
||||
def test_editor_can_edit_in_public_mode(datalayer, user):
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_anonymous_owner_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.owner = None
|
||||
map.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
||||
def test_owner_can_edit_in_inherit_mode_and_map_in_owner_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.OWNER
|
||||
map.save()
|
||||
assert datalayer.can_edit(map.owner)
|
||||
|
||||
|
||||
def test_editors_cannot_edit_in_inherit_mode_and_map_in_owner_mode(datalayer, user):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.edit_status = Map.OWNER
|
||||
map.save()
|
||||
assert not datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_inherit_mode_and_map_in_owner_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.OWNER
|
||||
map.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
||||
def test_owner_can_edit_in_inherit_mode_and_map_in_editors_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.EDITORS
|
||||
map.save()
|
||||
assert datalayer.can_edit(map.owner)
|
||||
|
||||
|
||||
def test_editors_can_edit_in_inherit_mode_and_map_in_editors_mode(datalayer, user):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.edit_status = Map.EDITORS
|
||||
map.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_inherit_mode_and_map_in_editors_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.EDITORS
|
||||
map.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
||||
def test_anonymous_can_edit_in_inherit_mode_and_map_in_public_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.ANONYMOUS
|
||||
map.save()
|
||||
assert datalayer.can_edit()
|
||||
|
|
|
@ -245,3 +245,143 @@ def test_update_readonly(client, datalayer, map, post_data, settings):
|
|||
client.login(username=map.owner.username, password="123123")
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_owner_can_edit_in_anonymous_owner_mode(
|
||||
datalayer, cookieclient, anonymap, post_data
|
||||
):
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = cookieclient.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_can_edit_in_anonymous_owner_but_public_mode(
|
||||
datalayer, client, anonymap, post_data
|
||||
):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_cannot_edit_in_anonymous_owner_mode(
|
||||
datalayer, client, anonymap, post_data
|
||||
):
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_owner_mode(datalayer, client, map, post_data):
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(map.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_anonymous_can_edit_in_owner_but_public_mode(datalayer, client, map, post_data):
|
||||
datalayer.edit_status = DataLayer.ANONYMOUS
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(map.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
def test_owner_can_edit_in_owner_mode(datalayer, client, map, post_data):
|
||||
client.login(username=map.owner.username, password="123123")
|
||||
datalayer.edit_status = DataLayer.OWNER
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(map.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
def test_editor_can_edit_in_editors_mode(datalayer, client, map, post_data):
|
||||
client.login(username=map.owner.username, password="123123")
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(map.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_owner_can_edit_if_inherit_and_map_in_owner_mode(
|
||||
datalayer, cookieclient, anonymap, post_data
|
||||
):
|
||||
anonymap.edit_status = Map.OWNER
|
||||
anonymap.save()
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = cookieclient.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_user_cannot_edit_if_inherit_and_map_in_owner_mode(
|
||||
datalayer, client, anonymap, post_data
|
||||
):
|
||||
anonymap.edit_status = Map.OWNER
|
||||
anonymap.save()
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_user_can_edit_if_inherit_and_map_in_public_mode(
|
||||
datalayer, client, anonymap, post_data
|
||||
):
|
||||
anonymap.edit_status = Map.ANONYMOUS
|
||||
anonymap.save()
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(anonymap.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
post_data["name"] = name
|
||||
response = client.post(url, post_data, follow=True)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
assert modified_datalayer.name == name
|
||||
|
|
|
@ -60,6 +60,13 @@ def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_
|
|||
assert map.can_edit(user, request)
|
||||
|
||||
|
||||
def test_anonymous_user_should_not_be_allowed_for_anonymous_map(map, user, rf): # noqa
|
||||
map.owner = None
|
||||
map.edit_status = map.OWNER
|
||||
map.save()
|
||||
assert not map.can_edit()
|
||||
|
||||
|
||||
def test_clone_should_return_new_instance(map, user):
|
||||
clone = map.clone()
|
||||
assert map.pk != clone.pk
|
||||
|
|
|
@ -128,9 +128,9 @@ def test_wrong_slug_should_redirect_to_canonical(client, map):
|
|||
|
||||
def test_wrong_slug_should_redirect_with_query_string(client, map):
|
||||
url = reverse("map", kwargs={"map_id": map.pk, "slug": "wrong-slug"})
|
||||
url = "{}?allowEdit=0".format(url)
|
||||
url = "{}?editMode=simple".format(url)
|
||||
canonical = reverse("map", kwargs={"map_id": map.pk, "slug": map.slug})
|
||||
canonical = "{}?allowEdit=0".format(canonical)
|
||||
canonical = "{}?editMode=simple".format(canonical)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 301
|
||||
assert response["Location"] == canonical
|
||||
|
@ -138,7 +138,7 @@ def test_wrong_slug_should_redirect_with_query_string(client, map):
|
|||
|
||||
def test_should_not_consider_the_query_string_for_canonical_check(client, map):
|
||||
url = reverse("map", kwargs={"map_id": map.pk, "slug": map.slug})
|
||||
url = "{}?allowEdit=0".format(url)
|
||||
url = "{}?editMode=simple".format(url)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
|
22
umap/urls.py
22
umap/urls.py
|
@ -13,7 +13,7 @@ from . import views
|
|||
from .decorators import (
|
||||
jsonize_view,
|
||||
login_required_if_not_anonymous_allowed,
|
||||
map_permissions_check,
|
||||
can_edit_map,
|
||||
can_view_map,
|
||||
)
|
||||
from .utils import decorated_patterns
|
||||
|
@ -144,16 +144,16 @@ map_urls = [
|
|||
views.DataLayerCreate.as_view(),
|
||||
name="datalayer_create",
|
||||
),
|
||||
re_path(
|
||||
r"^map/(?P<map_id>[\d]+)/datalayer/update/(?P<pk>\d+)/$",
|
||||
views.DataLayerUpdate.as_view(),
|
||||
name="datalayer_update",
|
||||
),
|
||||
re_path(
|
||||
r"^map/(?P<map_id>[\d]+)/datalayer/delete/(?P<pk>\d+)/$",
|
||||
views.DataLayerDelete.as_view(),
|
||||
name="datalayer_delete",
|
||||
),
|
||||
re_path(
|
||||
r"^map/(?P<map_id>[\d]+)/datalayer/permissions/(?P<pk>\d+)/$",
|
||||
views.UpdateDataLayerPermissions.as_view(),
|
||||
name="datalayer_permissions",
|
||||
),
|
||||
]
|
||||
if settings.FROM_EMAIL:
|
||||
map_urls.append(
|
||||
|
@ -163,7 +163,15 @@ if settings.FROM_EMAIL:
|
|||
name="map_send_edit_link",
|
||||
)
|
||||
)
|
||||
i18n_urls += decorated_patterns([map_permissions_check, never_cache], *map_urls)
|
||||
datalayer_urls = [
|
||||
re_path(
|
||||
r"^map/(?P<map_id>[\d]+)/datalayer/update/(?P<pk>\d+)/$",
|
||||
views.DataLayerUpdate.as_view(),
|
||||
name="datalayer_update",
|
||||
),
|
||||
]
|
||||
i18n_urls += decorated_patterns([can_edit_map, never_cache], *map_urls)
|
||||
i18n_urls += decorated_patterns([never_cache], *datalayer_urls)
|
||||
urlpatterns += i18n_patterns(
|
||||
re_path(r"^$", views.home, name="home"),
|
||||
re_path(
|
||||
|
|
|
@ -45,8 +45,10 @@ from .forms import (
|
|||
DEFAULT_LATITUDE,
|
||||
DEFAULT_LONGITUDE,
|
||||
DEFAULT_CENTER,
|
||||
AnonymousMapPermissionsForm,
|
||||
DataLayerForm,
|
||||
DataLayerPermissionsForm,
|
||||
AnonymousDataLayerPermissionsForm,
|
||||
AnonymousMapPermissionsForm,
|
||||
FlatErrorList,
|
||||
MapSettingsForm,
|
||||
SendLinkForm,
|
||||
|
@ -445,23 +447,33 @@ class MapDetailMixin:
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user = self.request.user
|
||||
properties = {
|
||||
"urls": _urls_for_js(),
|
||||
"tilelayers": TileLayer.get_list(),
|
||||
"allowEdit": self.is_edit_allowed(),
|
||||
"editMode": self.edit_mode,
|
||||
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
|
||||
"umap_id": self.get_umap_id(),
|
||||
"starred": self.is_starred(),
|
||||
"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 if i != Map.BLOCKED
|
||||
],
|
||||
"anonymous_edit_statuses": [
|
||||
(i, str(label)) for i, label in AnonymousMapPermissionsForm.STATUS
|
||||
],
|
||||
"umap_version": VERSION,
|
||||
}
|
||||
created = bool(getattr(self, "object", None))
|
||||
if (created and self.object.owner) or (not created and not user.is_anonymous):
|
||||
map_statuses = Map.EDIT_STATUS
|
||||
datalayer_statuses = DataLayer.EDIT_STATUS
|
||||
else:
|
||||
map_statuses = AnonymousMapPermissionsForm.STATUS
|
||||
datalayer_statuses = AnonymousDataLayerPermissionsForm.STATUS
|
||||
properties["edit_statuses"] = [
|
||||
(i, str(label)) for i, label in map_statuses
|
||||
]
|
||||
properties["datalayer_edit_statuses"] = [
|
||||
(i, str(label)) for i, label in datalayer_statuses
|
||||
]
|
||||
if self.get_short_url():
|
||||
properties["shortUrl"] = self.get_short_url()
|
||||
|
||||
|
@ -474,7 +486,6 @@ class MapDetailMixin:
|
|||
locale = to_locale(lang)
|
||||
properties["locale"] = locale
|
||||
context["locale"] = locale
|
||||
user = self.request.user
|
||||
if not user.is_anonymous:
|
||||
properties["user"] = {
|
||||
"id": user.pk,
|
||||
|
@ -492,8 +503,9 @@ class MapDetailMixin:
|
|||
def get_datalayers(self):
|
||||
return []
|
||||
|
||||
def is_edit_allowed(self):
|
||||
return True
|
||||
@property
|
||||
def edit_mode(self):
|
||||
return "advanced"
|
||||
|
||||
def get_umap_id(self):
|
||||
return None
|
||||
|
@ -551,11 +563,22 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
|||
return self.object.get_absolute_url()
|
||||
|
||||
def get_datalayers(self):
|
||||
datalayers = DataLayer.objects.filter(map=self.object)
|
||||
return [l.metadata for l in datalayers]
|
||||
return [
|
||||
l.metadata(self.request.user, self.request)
|
||||
for l in self.object.datalayer_set.all()
|
||||
]
|
||||
|
||||
def is_edit_allowed(self):
|
||||
return self.object.can_edit(self.request.user, self.request)
|
||||
@property
|
||||
def edit_mode(self):
|
||||
edit_mode = "disabled"
|
||||
if self.object.can_edit(self.request.user, self.request):
|
||||
edit_mode = "advanced"
|
||||
elif any(
|
||||
d.can_edit(self.request.user, self.request)
|
||||
for d in self.object.datalayer_set.all()
|
||||
):
|
||||
edit_mode = "simple"
|
||||
return edit_mode
|
||||
|
||||
def get_umap_id(self):
|
||||
return self.object.pk
|
||||
|
@ -883,7 +906,9 @@ class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView):
|
|||
form.instance.map = self.kwargs["map_inst"]
|
||||
self.object = form.save()
|
||||
# Simple response with only metadatas (including new id)
|
||||
response = simple_json_response(**self.object.metadata)
|
||||
response = simple_json_response(
|
||||
**self.object.metadata(self.request.user, self.request)
|
||||
)
|
||||
response["Last-Modified"] = self.last_modified
|
||||
return response
|
||||
|
||||
|
@ -896,7 +921,9 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|||
self.object = form.save()
|
||||
# Simple response with only metadatas (client should not reload all data
|
||||
# on save)
|
||||
response = simple_json_response(**self.object.metadata)
|
||||
response = simple_json_response(
|
||||
**self.object.metadata(self.request.user, self.request)
|
||||
)
|
||||
response["Last-Modified"] = self.last_modified
|
||||
return response
|
||||
|
||||
|
@ -911,7 +938,9 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if self.object.map != self.kwargs["map_inst"]:
|
||||
if self.object.map.pk != int(self.kwargs["map_id"]):
|
||||
return HttpResponseForbidden()
|
||||
if not self.object.can_edit(user=self.request.user, request=self.request):
|
||||
return HttpResponseForbidden()
|
||||
if not self.is_unmodified():
|
||||
return HttpResponse(status=412)
|
||||
|
@ -936,6 +965,21 @@ class DataLayerVersions(BaseDetailView):
|
|||
return simple_json_response(versions=self.object.versions)
|
||||
|
||||
|
||||
class UpdateDataLayerPermissions(FormLessEditMixin, UpdateView):
|
||||
model = DataLayer
|
||||
pk_url_kwarg = "pk"
|
||||
|
||||
def get_form_class(self):
|
||||
if self.object.map.owner:
|
||||
return DataLayerPermissionsForm
|
||||
else:
|
||||
return AnonymousDataLayerPermissionsForm
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
return simple_json_response(info=_("Permissions updated with success!"))
|
||||
|
||||
|
||||
# ############## #
|
||||
# Picto #
|
||||
# ############## #
|
||||
|
|
Loading…
Reference in a new issue