umap-project 2.8.0__py3-none-any.whl → 2.8.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- umap/__init__.py +1 -1
- umap/locale/en/LC_MESSAGES/django.po +13 -13
- umap/management/commands/empty_trash.py +2 -5
- umap/management/commands/migrate_to_S3.py +3 -3
- umap/settings/base.py +2 -2
- umap/static/umap/css/form.css +0 -3
- umap/static/umap/js/modules/data/features.js +4 -19
- umap/static/umap/js/modules/data/layer.js +19 -41
- umap/static/umap/js/modules/importer.js +20 -65
- umap/static/umap/js/modules/rendering/icon.js +1 -2
- umap/static/umap/js/modules/rendering/map.js +7 -7
- umap/static/umap/js/modules/rendering/popup.js +10 -9
- umap/static/umap/js/modules/rendering/template.js +9 -53
- umap/static/umap/js/modules/rendering/ui.js +2 -6
- umap/static/umap/js/modules/request.js +2 -2
- umap/static/umap/js/modules/schema.js +0 -1
- umap/static/umap/js/modules/ui/dialog.js +0 -5
- umap/static/umap/js/modules/umap.js +10 -33
- umap/static/umap/js/modules/utils.js +0 -2
- umap/static/umap/js/umap.controls.js +4 -7
- umap/static/umap/js/umap.forms.js +0 -44
- umap/static/umap/locale/en.js +1 -3
- umap/static/umap/locale/en.json +1 -3
- umap/static/umap/locale/fr.js +1 -3
- umap/static/umap/locale/fr.json +1 -3
- umap/static/umap/map.css +166 -34
- umap/static/umap/vars.css +1 -0
- umap/storage.py +216 -0
- umap/templates/base.html +0 -2
- umap/templates/umap/components/alerts/alert.html +0 -4
- umap/templates/umap/css.html +0 -3
- umap/templates/umap/js.html +0 -2
- umap/templates/umap/map_init.html +0 -2
- umap/templates/umap/user_dashboard.html +0 -2
- umap/tests/integration/test_edit_datalayer.py +0 -11
- umap/tests/integration/test_import.py +1 -20
- umap/tests/test_datalayer_s3.py +1 -1
- umap/tests/test_statics.py +1 -1
- umap/tests/test_team_views.py +1 -35
- umap/tests/test_views.py +74 -0
- umap/views.py +15 -20
- {umap_project-2.8.0.dist-info → umap_project-2.8.0a0.dist-info}/METADATA +1 -1
- {umap_project-2.8.0.dist-info → umap_project-2.8.0a0.dist-info}/RECORD +46 -52
- umap/settings/local_s3.py +0 -45
- umap/storage/__init__.py +0 -3
- umap/storage/fs.py +0 -101
- umap/storage/s3.py +0 -61
- umap/storage/staticfiles.py +0 -64
- umap/tests/fixtures/test_upload_simple_marker.json +0 -19
- umap/tests/test_dashboard.py +0 -82
- {umap_project-2.8.0.dist-info → umap_project-2.8.0a0.dist-info}/WHEEL +0 -0
- {umap_project-2.8.0.dist-info → umap_project-2.8.0a0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.0.dist-info → umap_project-2.8.0a0.dist-info}/licenses/LICENSE +0 -0
umap/storage.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import time
|
|
5
|
+
from gzip import GzipFile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
|
11
|
+
from django.core.files.storage import FileSystemStorage
|
|
12
|
+
from rcssmin import cssmin
|
|
13
|
+
from rjsmin import jsmin
|
|
14
|
+
from storages.backends.s3 import S3Storage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UmapManifestStaticFilesStorage(ManifestStaticFilesStorage):
|
|
18
|
+
support_js_module_import_aggregation = True
|
|
19
|
+
max_post_process_passes = 15
|
|
20
|
+
|
|
21
|
+
# We remove `;` at the end of all regexps to match our biome config.
|
|
22
|
+
_js_module_import_aggregation_patterns = (
|
|
23
|
+
"*.js",
|
|
24
|
+
(
|
|
25
|
+
(
|
|
26
|
+
(
|
|
27
|
+
r"""(?P<matched>import(?s:(?P<import>[\s\{].*?))"""
|
|
28
|
+
r"""\s*from\s*['"](?P<url>[\.\/].*?)["']\s*)"""
|
|
29
|
+
),
|
|
30
|
+
'import%(import)s from "%(url)s"\n',
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
(
|
|
34
|
+
r"""(?P<matched>export(?s:(?P<exports>[\s\{].*?))"""
|
|
35
|
+
r"""\s*from\s*["'](?P<url>[\.\/].*?)["']\s*)"""
|
|
36
|
+
),
|
|
37
|
+
'export%(exports)s from "%(url)s"\n',
|
|
38
|
+
),
|
|
39
|
+
(
|
|
40
|
+
r"""(?P<matched>import\s*['"](?P<url>[\.\/].*?)["']\s*)""",
|
|
41
|
+
'import"%(url)s"\n',
|
|
42
|
+
),
|
|
43
|
+
(
|
|
44
|
+
r"""(?P<matched>import\(["'](?P<url>.*?)["']\)\.then)""",
|
|
45
|
+
"""import("%(url)s").then""",
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
r"""(?P<matched>await import\(["'](?P<url>.*?)["']\))""",
|
|
49
|
+
"""await import("%(url)s")""",
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def post_process(self, paths, **options):
|
|
55
|
+
collected = super().post_process(paths, **options)
|
|
56
|
+
for original_path, processed_path, processed in collected:
|
|
57
|
+
if isinstance(processed, Exception):
|
|
58
|
+
print("Error with file", original_path)
|
|
59
|
+
raise processed
|
|
60
|
+
if processed_path.endswith(".js"):
|
|
61
|
+
path = Path(settings.STATIC_ROOT) / processed_path
|
|
62
|
+
initial = path.read_text()
|
|
63
|
+
if "sourceMappingURL" not in initial: # Already minified.
|
|
64
|
+
minified = jsmin(initial)
|
|
65
|
+
path.write_text(minified)
|
|
66
|
+
if processed_path.endswith(".css"):
|
|
67
|
+
path = Path(settings.STATIC_ROOT) / processed_path
|
|
68
|
+
initial = path.read_text()
|
|
69
|
+
if "sourceMappingURL" not in initial: # Already minified.
|
|
70
|
+
minified = cssmin(initial)
|
|
71
|
+
path.write_text(minified)
|
|
72
|
+
yield original_path, processed_path, True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class UmapS3(S3Storage):
|
|
76
|
+
gzip = True
|
|
77
|
+
|
|
78
|
+
def get_reference_version(self, instance):
|
|
79
|
+
metadata = self.connection.meta.client.head_object(
|
|
80
|
+
Bucket=self.bucket_name, Key=instance.geojson.name
|
|
81
|
+
)
|
|
82
|
+
# Do not fail if bucket does not handle versioning
|
|
83
|
+
return metadata.get("VersionId", metadata["ETag"])
|
|
84
|
+
|
|
85
|
+
def make_filename(self, instance):
|
|
86
|
+
return f"{str(instance.pk)}.geojson"
|
|
87
|
+
|
|
88
|
+
def list_versions(self, instance):
|
|
89
|
+
response = self.connection.meta.client.list_object_versions(
|
|
90
|
+
Bucket=self.bucket_name, Prefix=instance.geojson.name
|
|
91
|
+
)
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
"ref": version["VersionId"],
|
|
95
|
+
"at": version["LastModified"].timestamp() * 1000,
|
|
96
|
+
"size": version["Size"],
|
|
97
|
+
}
|
|
98
|
+
for version in response["Versions"]
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def get_version(self, ref, instance):
|
|
102
|
+
try:
|
|
103
|
+
data = self.connection.meta.client.get_object(
|
|
104
|
+
Bucket=self.bucket_name,
|
|
105
|
+
Key=instance.geojson.name,
|
|
106
|
+
VersionId=ref,
|
|
107
|
+
)
|
|
108
|
+
except ClientError:
|
|
109
|
+
raise ValueError(f"Invalid version reference: {ref}")
|
|
110
|
+
return GzipFile(mode="r", fileobj=data["Body"]).read()
|
|
111
|
+
|
|
112
|
+
def get_version_path(self, ref, instance):
|
|
113
|
+
return self.url(instance.geojson.name, parameters={"VersionId": ref})
|
|
114
|
+
|
|
115
|
+
def onDatalayerSave(self, instance):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
def onDatalayerDelete(self, instance):
|
|
119
|
+
return self.connection.meta.client.delete_object(
|
|
120
|
+
Bucket=self.bucket_name,
|
|
121
|
+
Key=instance.geojson.name,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class UmapFileSystem(FileSystemStorage):
|
|
126
|
+
def get_reference_version(self, instance):
|
|
127
|
+
return self._extract_version_ref(instance.geojson.name)
|
|
128
|
+
|
|
129
|
+
def make_filename(self, instance):
|
|
130
|
+
root = self._base_path(instance)
|
|
131
|
+
name = "%s_%s.geojson" % (instance.pk, int(time.time() * 1000))
|
|
132
|
+
return root / name
|
|
133
|
+
|
|
134
|
+
def list_versions(self, instance):
|
|
135
|
+
root = self._base_path(instance)
|
|
136
|
+
names = self.listdir(root)[1]
|
|
137
|
+
names = [name for name in names if self._is_valid_version(name, instance)]
|
|
138
|
+
versions = [self._version_metadata(name, instance) for name in names]
|
|
139
|
+
versions.sort(reverse=True, key=operator.itemgetter("at"))
|
|
140
|
+
return versions
|
|
141
|
+
|
|
142
|
+
def get_version(self, ref, instance):
|
|
143
|
+
with self.open(self.get_version_path(ref, instance), "r") as f:
|
|
144
|
+
return f.read()
|
|
145
|
+
|
|
146
|
+
def get_version_path(self, ref, instance):
|
|
147
|
+
base_path = Path(settings.MEDIA_ROOT) / self._base_path(instance)
|
|
148
|
+
fullpath = base_path / f"{instance.pk}_{ref}.geojson"
|
|
149
|
+
if instance.old_id and not fullpath.exists():
|
|
150
|
+
fullpath = base_path / f"{instance.old_id}_{ref}.geojson"
|
|
151
|
+
if not fullpath.exists():
|
|
152
|
+
raise ValueError(f"Invalid version reference: {ref}")
|
|
153
|
+
return fullpath
|
|
154
|
+
|
|
155
|
+
def onDatalayerSave(self, instance):
|
|
156
|
+
self._purge_gzip(instance)
|
|
157
|
+
self._purge_old_versions(instance, keep=settings.UMAP_KEEP_VERSIONS)
|
|
158
|
+
|
|
159
|
+
def onDatalayerDelete(self, instance):
|
|
160
|
+
self._purge_gzip(instance)
|
|
161
|
+
self._purge_old_versions(instance, keep=None)
|
|
162
|
+
|
|
163
|
+
def _extract_version_ref(self, path):
|
|
164
|
+
version = path.split(".")[0]
|
|
165
|
+
if "_" in version:
|
|
166
|
+
return version.split("_")[-1]
|
|
167
|
+
return version
|
|
168
|
+
|
|
169
|
+
def _base_path(self, instance):
|
|
170
|
+
path = ["datalayer", str(instance.map.pk)[-1]]
|
|
171
|
+
if len(str(instance.map.pk)) > 1:
|
|
172
|
+
path.append(str(instance.map.pk)[-2])
|
|
173
|
+
path.append(str(instance.map.pk))
|
|
174
|
+
return Path(os.path.join(*path))
|
|
175
|
+
|
|
176
|
+
def _is_valid_version(self, name, instance):
|
|
177
|
+
valid_prefixes = [name.startswith("%s_" % instance.pk)]
|
|
178
|
+
if instance.old_id:
|
|
179
|
+
valid_prefixes.append(name.startswith("%s_" % instance.old_id))
|
|
180
|
+
return any(valid_prefixes) and name.endswith(".geojson")
|
|
181
|
+
|
|
182
|
+
def _version_metadata(self, name, instance):
|
|
183
|
+
ref = self._extract_version_ref(name)
|
|
184
|
+
return {
|
|
185
|
+
"name": name,
|
|
186
|
+
"ref": ref,
|
|
187
|
+
"at": ref,
|
|
188
|
+
"size": self.size(self._base_path(instance) / name),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def _purge_old_versions(self, instance, keep=None):
|
|
192
|
+
root = self._base_path(instance)
|
|
193
|
+
versions = self.list_versions(instance)
|
|
194
|
+
if keep is not None:
|
|
195
|
+
versions = versions[keep:]
|
|
196
|
+
for version in versions:
|
|
197
|
+
name = version["name"]
|
|
198
|
+
# Should not be in the list, but ensure to not delete the file
|
|
199
|
+
# currently used in database
|
|
200
|
+
if keep is not None and instance.geojson.name.endswith(name):
|
|
201
|
+
continue
|
|
202
|
+
try:
|
|
203
|
+
self.delete(root / name)
|
|
204
|
+
except FileNotFoundError:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
def _purge_gzip(self, instance):
|
|
208
|
+
root = self._base_path(instance)
|
|
209
|
+
names = self.listdir(root)[1]
|
|
210
|
+
prefixes = [f"{instance.pk}_"]
|
|
211
|
+
if instance.old_id:
|
|
212
|
+
prefixes.append(f"{instance.old_id}_")
|
|
213
|
+
prefixes = tuple(prefixes)
|
|
214
|
+
for name in names:
|
|
215
|
+
if name.startswith(prefixes) and name.endswith(".gz"):
|
|
216
|
+
self.delete(root / name)
|
umap/templates/base.html
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
<meta name="viewport"
|
|
19
19
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
20
20
|
{# See https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs #}
|
|
21
|
-
{% autoescape off %}
|
|
22
21
|
<link rel="icon"
|
|
23
22
|
href="{% static 'umap/favicons/favicon.ico' %}"
|
|
24
23
|
sizes="32x32">
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
href="{% static 'umap/favicons/apple-touch-icon.png' %}">
|
|
30
29
|
<!-- 180×180 -->
|
|
31
30
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
32
|
-
{% endautoescape %}
|
|
33
31
|
</head>
|
|
34
32
|
<body class="{% block body_class %}{% endblock body_class %}">
|
|
35
33
|
{% block header %}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
{% load i18n static %}
|
|
2
2
|
|
|
3
|
-
{% autoescape off %}
|
|
4
3
|
<style type="text/css">
|
|
5
4
|
@import "{% static 'umap/js/components/alerts/alert.css' %}";
|
|
6
5
|
</style>
|
|
7
|
-
{% endautoescape %}
|
|
8
6
|
<template id="umap-alert-template">
|
|
9
7
|
<div role="dialog" class="dark window umap-alert">
|
|
10
8
|
<div>
|
|
@@ -99,7 +97,6 @@
|
|
|
99
97
|
</div>
|
|
100
98
|
</template>
|
|
101
99
|
<umap-alert-conflict></umap-alert-conflict>
|
|
102
|
-
{% autoescape off %}
|
|
103
100
|
<script type="module">
|
|
104
101
|
import { register } from '{% static 'umap/js/components/base.js' %}'
|
|
105
102
|
import {
|
|
@@ -111,4 +108,3 @@
|
|
|
111
108
|
register(uMapAlertCreation, 'umap-alert-creation')
|
|
112
109
|
register(uMapAlertConflict, 'umap-alert-conflict')
|
|
113
110
|
</script>
|
|
114
|
-
{% endautoescape %}
|
umap/templates/umap/css.html
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
{% load static %}
|
|
2
2
|
|
|
3
|
-
{% autoescape off %}
|
|
4
|
-
|
|
5
3
|
<link rel="stylesheet"
|
|
6
4
|
href="{% static 'umap/vendors/leaflet/leaflet.css' %}" />
|
|
7
5
|
<link rel="stylesheet"
|
|
@@ -41,4 +39,3 @@
|
|
|
41
39
|
<link rel="stylesheet" href="{% static 'umap/css/tableeditor.css' %}" />
|
|
42
40
|
<link rel="stylesheet" href="{% static 'umap/css/bar.css' %}" />
|
|
43
41
|
<link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
|
|
44
|
-
{% endautoescape %}
|
umap/templates/umap/js.html
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
{% load static %}
|
|
2
2
|
|
|
3
|
-
{% autoescape off %}
|
|
4
3
|
<script type="module"
|
|
5
4
|
src="{% static 'umap/vendors/leaflet/leaflet-src.esm.js' %}"
|
|
6
5
|
defer></script>
|
|
@@ -43,4 +42,3 @@
|
|
|
43
42
|
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
|
|
44
43
|
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
|
45
44
|
<script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
|
46
|
-
{% endautoescape %}
|
|
@@ -46,9 +46,7 @@
|
|
|
46
46
|
{% block bottom_js %}
|
|
47
47
|
{{ block.super }}
|
|
48
48
|
<script type="module">
|
|
49
|
-
{% autoescape off %}
|
|
50
49
|
import Umap from '{% static "umap/js/modules/umap.js" %}'
|
|
51
|
-
{% endautoescape %}
|
|
52
50
|
const CACHE = {}
|
|
53
51
|
for (const mapOpener of document.querySelectorAll("button.map-opener")) {
|
|
54
52
|
mapOpener.addEventListener('click', (event) => {
|
|
@@ -221,14 +221,3 @@ def test_deleting_datalayer_should_remove_from_caption(
|
|
|
221
221
|
page.locator(".panel.right").get_by_title("Delete layer").click()
|
|
222
222
|
page.get_by_role("button", name="OK").click()
|
|
223
223
|
expect(panel.get_by_text("test datalayer")).to_be_hidden()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def test_can_edit_datalayer_name_in_list(live_server, openmap, datalayer, page):
|
|
227
|
-
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
228
|
-
page.get_by_role("link", name="Manage layers").click()
|
|
229
|
-
page.get_by_text("test datalayer").click()
|
|
230
|
-
page.get_by_text("test datalayer").fill("test datalayer foobar")
|
|
231
|
-
page.get_by_role("button", name="Open browser").click()
|
|
232
|
-
expect(
|
|
233
|
-
page.locator(".panel.left").get_by_text("test datalayer foobar")
|
|
234
|
-
).to_be_visible()
|
|
@@ -9,6 +9,7 @@ from playwright.sync_api import expect
|
|
|
9
9
|
|
|
10
10
|
from umap.models import DataLayer
|
|
11
11
|
|
|
12
|
+
from ..base import mock_tiles
|
|
12
13
|
from .helpers import save_and_get_json
|
|
13
14
|
|
|
14
15
|
pytestmark = pytest.mark.django_db
|
|
@@ -764,23 +765,3 @@ def test_import_georss_from_textarea(tilelayer, live_server, page):
|
|
|
764
765
|
# A layer has been created
|
|
765
766
|
expect(layers).to_have_count(1)
|
|
766
767
|
expect(markers).to_have_count(1)
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
def test_import_from_multiple_files(live_server, page, tilelayer):
|
|
770
|
-
page.goto(f"{live_server.url}/map/new/")
|
|
771
|
-
page.get_by_title("Import data").click()
|
|
772
|
-
file_input = page.locator("input[type='file']")
|
|
773
|
-
with page.expect_file_chooser() as fc_info:
|
|
774
|
-
file_input.click()
|
|
775
|
-
file_chooser = fc_info.value
|
|
776
|
-
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
|
777
|
-
paths = [
|
|
778
|
-
FIXTURES / "test_upload_data.json",
|
|
779
|
-
FIXTURES / "test_upload_simple_marker.json",
|
|
780
|
-
]
|
|
781
|
-
file_chooser.set_files(paths)
|
|
782
|
-
markers = page.locator(".leaflet-marker-icon")
|
|
783
|
-
expect(markers).to_have_count(0)
|
|
784
|
-
page.get_by_role("button", name="Import data", exact=True).click()
|
|
785
|
-
# Two in one file, one in the other
|
|
786
|
-
expect(markers).to_have_count(3)
|
umap/tests/test_datalayer_s3.py
CHANGED
umap/tests/test_statics.py
CHANGED
|
@@ -15,7 +15,7 @@ def staticfiles(settings):
|
|
|
15
15
|
# Make sure settings are properly reset after the test
|
|
16
16
|
settings.STORAGES = deepcopy(settings.STORAGES)
|
|
17
17
|
settings.STORAGES["staticfiles"]["BACKEND"] = (
|
|
18
|
-
"umap.storage.
|
|
18
|
+
"umap.storage.UmapManifestStaticFilesStorage"
|
|
19
19
|
)
|
|
20
20
|
try:
|
|
21
21
|
call_command("collectstatic", "--noinput")
|
umap/tests/test_team_views.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from django.urls import reverse
|
|
3
3
|
|
|
4
|
-
from umap.models import
|
|
4
|
+
from umap.models import Team
|
|
5
5
|
|
|
6
6
|
pytestmark = pytest.mark.django_db
|
|
7
7
|
|
|
@@ -15,40 +15,6 @@ def test_can_see_team_maps(client, map, team):
|
|
|
15
15
|
assert map.name in response.content.decode()
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
@pytest.mark.parametrize("share_status", [Map.PRIVATE, Map.DRAFT])
|
|
19
|
-
def test_others_cannot_see_team_private_maps_in_team_page(
|
|
20
|
-
client, map, team, user, share_status
|
|
21
|
-
):
|
|
22
|
-
map.team = team
|
|
23
|
-
map.share_status = share_status
|
|
24
|
-
map.save()
|
|
25
|
-
url = reverse("team_maps", args=(team.pk,))
|
|
26
|
-
response = client.get(url)
|
|
27
|
-
assert response.status_code == 200
|
|
28
|
-
assert map.name not in response.content.decode()
|
|
29
|
-
# User is not in team
|
|
30
|
-
client.login(username=user.username, password="123123")
|
|
31
|
-
response = client.get(url)
|
|
32
|
-
assert response.status_code == 200
|
|
33
|
-
assert map.name not in response.content.decode()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@pytest.mark.parametrize("share_status", [Map.PRIVATE, Map.DRAFT])
|
|
37
|
-
def test_members_can_see_private_maps_in_team_page(
|
|
38
|
-
client, map, team, user, share_status
|
|
39
|
-
):
|
|
40
|
-
map.team = team
|
|
41
|
-
map.share_status = share_status
|
|
42
|
-
map.save()
|
|
43
|
-
user.teams.add(team)
|
|
44
|
-
user.save()
|
|
45
|
-
url = reverse("team_maps", args=(team.pk,))
|
|
46
|
-
client.login(username=user.username, password="123123")
|
|
47
|
-
response = client.get(url)
|
|
48
|
-
assert response.status_code == 200
|
|
49
|
-
assert map.name in response.content.decode()
|
|
50
|
-
|
|
51
|
-
|
|
52
18
|
def test_user_can_see_their_teams(client, team, user):
|
|
53
19
|
user.teams.add(team)
|
|
54
20
|
user.save()
|
umap/tests/test_views.py
CHANGED
|
@@ -267,6 +267,80 @@ def test_change_user_slug(client, user, settings):
|
|
|
267
267
|
assert f"/en/user/{user.pk}/" in response.content.decode()
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
@pytest.mark.django_db
|
|
271
|
+
def test_user_dashboard_is_restricted_to_logged_in(client):
|
|
272
|
+
response = client.get(reverse("user_dashboard"))
|
|
273
|
+
assert response.status_code == 302
|
|
274
|
+
assert response["Location"] == "/en/login/?next=/en/me"
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@pytest.mark.django_db
|
|
278
|
+
def test_user_dashboard_display_user_maps(client, map):
|
|
279
|
+
client.login(username=map.owner.username, password="123123")
|
|
280
|
+
response = client.get(reverse("user_dashboard"))
|
|
281
|
+
assert response.status_code == 200
|
|
282
|
+
body = response.content.decode()
|
|
283
|
+
assert map.name in body
|
|
284
|
+
assert f"{map.get_absolute_url()}?edit" in body
|
|
285
|
+
assert f"{map.get_absolute_url()}?share" in body
|
|
286
|
+
assert f"/map/{map.pk}/download" in body
|
|
287
|
+
assert "Everyone (public)" in body
|
|
288
|
+
assert "Owner only" in body
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@pytest.mark.django_db
|
|
292
|
+
def test_user_dashboard_do_not_display_blocked_user_maps(client, map):
|
|
293
|
+
map.share_status = Map.BLOCKED
|
|
294
|
+
map.save()
|
|
295
|
+
client.login(username=map.owner.username, password="123123")
|
|
296
|
+
response = client.get(reverse("user_dashboard"))
|
|
297
|
+
assert response.status_code == 200
|
|
298
|
+
body = response.content.decode()
|
|
299
|
+
assert map.name not in body
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@pytest.mark.django_db
|
|
303
|
+
def test_user_dashboard_do_not_display_deleted_user_maps(client, map):
|
|
304
|
+
map.share_status = Map.DELETED
|
|
305
|
+
map.save()
|
|
306
|
+
client.login(username=map.owner.username, password="123123")
|
|
307
|
+
response = client.get(reverse("user_dashboard"))
|
|
308
|
+
assert response.status_code == 200
|
|
309
|
+
body = response.content.decode()
|
|
310
|
+
assert map.name not in body
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@pytest.mark.django_db
|
|
314
|
+
def test_user_dashboard_display_user_team_maps(client, map, team, user):
|
|
315
|
+
user.teams.add(team)
|
|
316
|
+
user.save()
|
|
317
|
+
map.team = team
|
|
318
|
+
map.save()
|
|
319
|
+
client.login(username=user.username, password="123123")
|
|
320
|
+
response = client.get(reverse("user_dashboard"))
|
|
321
|
+
assert response.status_code == 200
|
|
322
|
+
body = response.content.decode()
|
|
323
|
+
assert map.name in body
|
|
324
|
+
assert map.get_absolute_url() in body
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@pytest.mark.django_db
|
|
328
|
+
def test_user_dashboard_display_user_maps_distinct(client, map):
|
|
329
|
+
# cf https://github.com/umap-project/umap/issues/1325
|
|
330
|
+
anonymap = MapFactory(name="Map witout owner should not appear")
|
|
331
|
+
user1 = UserFactory(username="user1")
|
|
332
|
+
user2 = UserFactory(username="user2")
|
|
333
|
+
map.editors.add(user1)
|
|
334
|
+
map.editors.add(user2)
|
|
335
|
+
map.save()
|
|
336
|
+
client.login(username=map.owner.username, password="123123")
|
|
337
|
+
response = client.get(reverse("user_dashboard"))
|
|
338
|
+
assert response.status_code == 200
|
|
339
|
+
body = response.content.decode()
|
|
340
|
+
assert body.count(f'<a href="/en/map/test-map_{map.pk}">test map</a>') == 1
|
|
341
|
+
assert body.count(anonymap.name) == 0
|
|
342
|
+
|
|
343
|
+
|
|
270
344
|
@pytest.mark.django_db
|
|
271
345
|
def test_logout_should_return_redirect(client, user, settings):
|
|
272
346
|
client.login(username=user.username, password="123123")
|
umap/views.py
CHANGED
|
@@ -317,11 +317,7 @@ class TeamMaps(PaginatorMixin, DetailView):
|
|
|
317
317
|
context_object_name = "current_team"
|
|
318
318
|
|
|
319
319
|
def get_maps(self):
|
|
320
|
-
|
|
321
|
-
user = self.request.user
|
|
322
|
-
if user.is_authenticated and user in self.object.users.all():
|
|
323
|
-
qs = Map.objects
|
|
324
|
-
return qs.filter(team=self.object).order_by("-modified_at")
|
|
320
|
+
return Map.public.filter(team=self.object).order_by("-modified_at")
|
|
325
321
|
|
|
326
322
|
def get_context_data(self, **kwargs):
|
|
327
323
|
kwargs.update(
|
|
@@ -456,27 +452,27 @@ showcase = MapsShowCase.as_view()
|
|
|
456
452
|
|
|
457
453
|
|
|
458
454
|
def validate_url(request):
|
|
459
|
-
assert request.method == "GET"
|
|
455
|
+
assert request.method == "GET"
|
|
460
456
|
url = request.GET.get("url")
|
|
461
|
-
assert url
|
|
457
|
+
assert url
|
|
462
458
|
try:
|
|
463
459
|
URLValidator(url)
|
|
464
|
-
except ValidationError
|
|
465
|
-
raise AssertionError(
|
|
466
|
-
assert "HTTP_REFERER" in request.META
|
|
460
|
+
except ValidationError:
|
|
461
|
+
raise AssertionError()
|
|
462
|
+
assert "HTTP_REFERER" in request.META
|
|
467
463
|
referer = urlparse(request.META.get("HTTP_REFERER"))
|
|
468
464
|
toproxy = urlparse(url)
|
|
469
465
|
local = urlparse(settings.SITE_URL)
|
|
470
|
-
assert toproxy.hostname
|
|
471
|
-
assert referer.hostname == local.hostname
|
|
472
|
-
assert toproxy.hostname != "localhost"
|
|
473
|
-
assert toproxy.netloc != local.netloc
|
|
466
|
+
assert toproxy.hostname
|
|
467
|
+
assert referer.hostname == local.hostname
|
|
468
|
+
assert toproxy.hostname != "localhost"
|
|
469
|
+
assert toproxy.netloc != local.netloc
|
|
474
470
|
try:
|
|
475
471
|
# clean this when in python 3.4
|
|
476
472
|
ipaddress = socket.gethostbyname(toproxy.hostname)
|
|
477
|
-
except
|
|
478
|
-
raise AssertionError(
|
|
479
|
-
assert not PRIVATE_IP.match(ipaddress)
|
|
473
|
+
except:
|
|
474
|
+
raise AssertionError()
|
|
475
|
+
assert not PRIVATE_IP.match(ipaddress)
|
|
480
476
|
return url
|
|
481
477
|
|
|
482
478
|
|
|
@@ -484,8 +480,7 @@ class AjaxProxy(View):
|
|
|
484
480
|
def get(self, *args, **kwargs):
|
|
485
481
|
try:
|
|
486
482
|
url = validate_url(self.request)
|
|
487
|
-
except AssertionError
|
|
488
|
-
print(f"AjaxProxy: {err}")
|
|
483
|
+
except AssertionError:
|
|
489
484
|
return HttpResponseBadRequest()
|
|
490
485
|
try:
|
|
491
486
|
ttl = int(self.request.GET.get("ttl"))
|
|
@@ -1173,7 +1168,7 @@ class DataLayerView(BaseDetailView):
|
|
|
1173
1168
|
# (no gzip/cache-control/If-Modified-Since/If-None-Match)
|
|
1174
1169
|
data = self.filedata
|
|
1175
1170
|
response = HttpResponse(data, content_type="application/geo+json")
|
|
1176
|
-
|
|
1171
|
+
response["X-Datalayer-Version"] = self.fileversion
|
|
1177
1172
|
return response
|
|
1178
1173
|
|
|
1179
1174
|
|