Merge branch 'master' into dependabot/pip/django-5.0.1
This commit is contained in:
commit
5da9b67b2a
36 changed files with 2143 additions and 78 deletions
11
.eslintrc.json
Normal file
11
.eslintrc.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"plugins": ["compat"],
|
||||
"extends": ["plugin:compat/recommended"],
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
1
.github/workflows/test-docs.yml
vendored
1
.github/workflows/test-docs.yml
vendored
|
@ -57,6 +57,7 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install -e .[test,dev]
|
||||
make installjs
|
||||
|
||||
- name: Run Lint
|
||||
run: make lint
|
||||
|
|
3
Makefile
3
Makefile
|
@ -20,9 +20,10 @@ format: ## Format the code and templates files
|
|||
|
||||
.PHONY: lint
|
||||
lint: ## Lint the code and template files
|
||||
npx eslint umap/static/umap/ &&\
|
||||
djlint umap/templates --lint &&\
|
||||
isort --check --profile black umap/ &&\
|
||||
ruff format --check --target-version=py310 umap/ &&\
|
||||
ruff format --check --target-version=py310 umap/ &&\
|
||||
vermin --no-tips --violations -t=3.10- umap/
|
||||
|
||||
docs: ## Compile the docs
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
- `umap/templates/umap/map_table.html`
|
||||
- `umap/templates/umap/user_dashboard.html`
|
||||
|
||||
[See the diff](https://github.com/umap-project/umap/compare/1.12.2...1.13.0#diff-1311890945256dbddf0e59928c2e9d4f59fd6bcc6b1fd33719ef35f03e5168b4).
|
||||
[See the diff](https://github.com/umap-project/umap/compare/1.12.2...1.13.0#files_bucket).
|
||||
|
||||
## 1.12.2 - 2023-12-29
|
||||
|
||||
|
|
|
@ -203,6 +203,13 @@ ready for production use (no backup, etc.)
|
|||
|
||||
Link to show on the header under the "Feedback and help" label.
|
||||
|
||||
#### UMAP_HOME_FEED
|
||||
|
||||
Which feed to display on the home page. Three valid values:
|
||||
- `"latest"`, which shows the latest maps (default)
|
||||
- `"highlighted"`, which shows the maps that have been starred by a staff member
|
||||
- `None`, which does not show any map on the home page
|
||||
|
||||
#### UMAP_MAPS_PER_PAGE
|
||||
|
||||
How many maps to show in maps list, like search or home page.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# How to make a release
|
||||
# Releases
|
||||
|
||||
## How to make a release
|
||||
|
||||
1. Run tests:
|
||||
- `make test`
|
||||
|
@ -20,12 +22,35 @@
|
|||
9. `make publish`
|
||||
10. `make docker`
|
||||
|
||||
## Deploying instances
|
||||
### Deploying instances
|
||||
|
||||
### OSMfr
|
||||
#### OSMfr
|
||||
|
||||
The process is manual for now, Yohan has one Makefile on his computer.
|
||||
|
||||
### ANCT
|
||||
#### ANCT
|
||||
|
||||
Update the [Dockerfile](https://gitlab.com/incubateur-territoires/startups/donnees-et-territoires/umap-dsfr-moncomptepro/-/blob/main/Dockerfile?ref_type=heads) with correct version and put a tag `YYYY.MM.DD` in order to deploy it to production.
|
||||
|
||||
|
||||
## When to make a release
|
||||
|
||||
We aim to support [Baseline](https://developer.mozilla.org/en-US/blog/baseline-evolution-on-mdn/) “Widely available” (implemented in major browsers within the last 30 months).
|
||||
|
||||
### Major (2.Y.Z)
|
||||
|
||||
* when we bump Django to a major version
|
||||
* when we change how we store data (both in database and filesystem)
|
||||
|
||||
### Minor (X.3.Z)
|
||||
|
||||
* when we add new features
|
||||
* when we improve an existing feature
|
||||
* when we improve the usability
|
||||
* when we change templates
|
||||
|
||||
If it's not a major nor a patch, it's a minor.
|
||||
|
||||
### Patch (X.Y.12)
|
||||
|
||||
* when there are bugfixes
|
||||
|
|
1715
package-lock.json
generated
1715
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"happen": "~0.1.3",
|
||||
"lebab": "^3.2.1",
|
||||
"mocha": "^10.2.0",
|
||||
|
@ -60,5 +62,8 @@
|
|||
"simple-statistics": "^7.8.3",
|
||||
"togpx": "^0.5.4",
|
||||
"tokml": "0.4.0"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ dev = [
|
|||
"djlint==1.34.1",
|
||||
"mkdocs==1.5.3",
|
||||
"mkdocs-material==9.4.14",
|
||||
"vermin==1.5.2",
|
||||
"vermin==1.6.0",
|
||||
"pymdown-extensions==10.4",
|
||||
"isort==5.12",
|
||||
]
|
||||
|
|
|
@ -216,7 +216,7 @@ class Map(NamedModel):
|
|||
"umap_id": self.pk,
|
||||
"onLoadPanel": "none",
|
||||
"captionBar": False,
|
||||
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL,
|
||||
"default_iconUrl": "%sumap/img/marker.svg" % settings.STATIC_URL,
|
||||
"slideshow": {},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -34,10 +34,7 @@ if path:
|
|||
for key in dir(d):
|
||||
if key.isupper():
|
||||
value = getattr(d, key)
|
||||
if key.startswith("LEAFLET_STORAGE"):
|
||||
# Retrocompat pre 1.0, remove me in 1.1.
|
||||
globals()["UMAP" + key[15:]] = value
|
||||
elif key == "UMAP_CUSTOM_TEMPLATES":
|
||||
if key == "UMAP_CUSTOM_TEMPLATES":
|
||||
if "DIRS" in globals()["TEMPLATES"][0]:
|
||||
globals()["TEMPLATES"][0]["DIRS"].insert(0, value)
|
||||
else:
|
||||
|
|
|
@ -255,6 +255,7 @@ DATABASES = {"default": env.db(default="postgis://localhost:5432/umap")}
|
|||
UMAP_DEFAULT_SHARE_STATUS = None
|
||||
UMAP_DEFAULT_EDIT_STATUS = None
|
||||
UMAP_DEFAULT_FEATURES_HAVE_OWNERS = False
|
||||
UMAP_HOME_FEED = "latest"
|
||||
|
||||
UMAP_READONLY = env("UMAP_READONLY", default=False)
|
||||
UMAP_GZIP = True
|
||||
|
|
4
umap/static/umap/img/marker.svg
Normal file
4
umap/static/umap/img/marker.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" id="circle" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
|
||||
<path d="M14,7.5c0,3.5899-2.9101,6.5-6.5,6.5S1,11.0899,1,7.5S3.9101,1,7.5,1S14,3.9101,14,7.5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 254 B |
|
@ -110,13 +110,13 @@ L.U.Browser = L.Class.extend({
|
|||
const formContainer = L.DomUtil.create('div', '', container)
|
||||
const dataContainer = L.DomUtil.create('div', 'umap-browse-features', container)
|
||||
|
||||
const appendAll = () => {
|
||||
const rebuildHTML = () => {
|
||||
dataContainer.innerHTML = ''
|
||||
this.map.eachBrowsableDataLayer((datalayer) => {
|
||||
this.addDatalayer(datalayer, dataContainer)
|
||||
})
|
||||
}
|
||||
const resetLayers = () => {
|
||||
const redrawDataLayers = () => {
|
||||
this.map.eachBrowsableDataLayer((datalayer) => {
|
||||
datalayer.resetLayer(true)
|
||||
})
|
||||
|
@ -129,16 +129,16 @@ L.U.Browser = L.Class.extend({
|
|||
makeDirty: false,
|
||||
callback: (e) => {
|
||||
if (e.helper.field === 'options.inBbox') {
|
||||
if (this.options.inBbox) this.map.on('moveend', appendAll)
|
||||
else this.map.off('moveend', appendAll)
|
||||
if (this.options.inBbox) this.map.on('moveend', rebuildHTML)
|
||||
else this.map.off('moveend', rebuildHTML)
|
||||
}
|
||||
appendAll()
|
||||
resetLayers()
|
||||
redrawDataLayers()
|
||||
rebuildHTML()
|
||||
},
|
||||
})
|
||||
formContainer.appendChild(builder.build())
|
||||
|
||||
appendAll()
|
||||
rebuildHTML()
|
||||
|
||||
this.map.ui.openPanel({
|
||||
data: { html: container },
|
||||
|
|
|
@ -1192,7 +1192,7 @@ L.U.AttributionControl = L.Control.Attribution.extend({
|
|||
'',
|
||||
container,
|
||||
` — ${L._('Powered by uMap')}`,
|
||||
'https://github.com/umap-project/umap/'
|
||||
'https://umap-project.org/'
|
||||
)
|
||||
}
|
||||
L.DomUtil.createLink('attribution-toggle', this._container, '')
|
||||
|
|
|
@ -548,6 +548,7 @@ L.U.Help = L.Class.extend({
|
|||
label.title = label.textContent = L._('Close')
|
||||
this.content = L.DomUtil.create('div', 'umap-help-content', this.box)
|
||||
this.isMacOS = /mac/i.test(
|
||||
// eslint-disable-next-line compat/compat -- Fallback available.
|
||||
navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform
|
||||
)
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@ L.U.Importer = L.Class.extend({
|
|||
{ type: 'file', multiple: 'multiple', autofocus: true },
|
||||
this.fileBox
|
||||
)
|
||||
this.map.ui.once('panel:closed', () => (this.fileInput.value = null))
|
||||
this.urlInput = L.DomUtil.element(
|
||||
'input',
|
||||
{ type: 'text', placeholder: L._('Provide an URL here') },
|
||||
|
|
|
@ -122,6 +122,11 @@ L.U.Map.include({
|
|||
`${this.HIDDABLE_CONTROLS[i]}Control`
|
||||
)
|
||||
}
|
||||
// Specific case for datalayersControl
|
||||
// which accept "expanded" value, on top of true/false/null
|
||||
if (L.Util.queryString('datalayersControl') === 'expanded') {
|
||||
L.Util.setFromQueryString(this.options, 'datalayersControl')
|
||||
}
|
||||
this.datalayersOnLoad = L.Util.queryString('datalayers')
|
||||
this.options.onLoadPanel = L.Util.queryString(
|
||||
'onLoadPanel',
|
||||
|
|
|
@ -194,6 +194,7 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({
|
|||
let mode = this.datalayer.options.choropleth.mode,
|
||||
classes = +this.datalayer.options.choropleth.classes || 5,
|
||||
breaks
|
||||
classes = Math.min(classes, values.length)
|
||||
if (mode === 'manual') {
|
||||
const manualBreaks = this.datalayer.options.choropleth.breaks
|
||||
if (manualBreaks) {
|
||||
|
|
|
@ -1322,10 +1322,10 @@ a.add-datalayer:hover,
|
|||
margin: 5px;
|
||||
}
|
||||
a[href^='mailto']::before {
|
||||
content: '🖃 ';
|
||||
content: '✉︎ ';
|
||||
}
|
||||
a[href^='tel']::before {
|
||||
content: '🕿 ';
|
||||
content: '☎︎ ';
|
||||
}
|
||||
address span {
|
||||
padding-right: 5px;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"after": true,
|
||||
"it": true,
|
||||
"sinon": true,
|
||||
"qs": true,
|
||||
"enableEdit": true,
|
||||
"disableEdit": true,
|
||||
"changeInputValue": true,
|
||||
|
|
|
@ -132,7 +132,7 @@ function initMap(options) {
|
|||
map_update_permissions: '/map/{map_id}/update/permissions/',
|
||||
map_download: '/map/{map_id}/download/',
|
||||
},
|
||||
default_iconUrl: '../src/img/marker.png',
|
||||
default_iconUrl: '../src/img/marker.svg',
|
||||
zoom: 6,
|
||||
share_statuses: [
|
||||
[1, 'Tout le monde (public)'],
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="wrapper">
|
||||
<h2 class="section">{% blocktrans %}Get inspired, browse maps{% endblocktrans %}</h2>
|
||||
<div class="map_list row">{% include "umap/map_list.html" %}</div>
|
||||
{% if maps %}
|
||||
<h2 class="section">{% blocktrans %}Get inspired, browse maps{% endblocktrans %}</h2>
|
||||
<div class="map_list row">{% include "umap/map_list.html" %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock maincontent %}
|
||||
|
|
|
@ -7,9 +7,17 @@
|
|||
map_detail
|
||||
{% endblock body_class %}
|
||||
{% block extra_head %}
|
||||
{% if preconnect_domains %}
|
||||
{% for domain in preconnect_domains %}
|
||||
<link rel="preconnect" href="{{ domain }}" />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% umap_css %}
|
||||
{% umap_js locale=locale %}
|
||||
{% if object.share_status != object.PUBLIC %}<meta name="robots" content="noindex">{% endif %}
|
||||
<link rel="alternate" type="application/json+oembed"
|
||||
href="{{ oembed_absolute_uri }}?url={{ absolute_uri|urlencode }}&format=json"
|
||||
title="{{ map.name }} oEmbed URL" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
{% block map_init %}
|
||||
|
|
|
@ -81,7 +81,7 @@ class MapFactory(factory.django.DjangoModelFactory):
|
|||
"attribution": "\xa9 OSM Contributors",
|
||||
"maxZoom": 18,
|
||||
"minZoom": 0,
|
||||
"url_template": "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
"url_template": "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
},
|
||||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
|
|
|
@ -13,12 +13,12 @@ DATALAYER_DATA = {
|
|||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {"name": "one point in france"},
|
||||
"properties": {"name": "one point in france", "foo": "point"},
|
||||
"geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]},
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {"name": "one polygon in greenland"},
|
||||
"properties": {"name": "one polygon in greenland", "foo": "polygon"},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
|
@ -34,7 +34,7 @@ DATALAYER_DATA = {
|
|||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {"name": "one line in new zeland"},
|
||||
"properties": {"name": "one line in new zeland", "foo": "line"},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
|
@ -72,14 +72,28 @@ def test_data_browser_should_be_open(live_server, page, bootstrap, map):
|
|||
def test_data_browser_should_be_filterable(live_server, page, bootstrap, map):
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
markers = page.locator(".leaflet-marker-icon")
|
||||
paths = page.locator(".leaflet-overlay-pane path")
|
||||
expect(markers).to_have_count(1)
|
||||
el = page.locator("input[name='filter']")
|
||||
expect(el).to_be_visible()
|
||||
el.type("poly")
|
||||
expect(paths).to_have_count(2)
|
||||
filter_ = page.locator("input[name='filter']")
|
||||
expect(filter_).to_be_visible()
|
||||
filter_.type("poly")
|
||||
expect(page.get_by_text("one point in france")).to_be_hidden()
|
||||
expect(page.get_by_text("one line in new zeland")).to_be_hidden()
|
||||
expect(page.get_by_text("one polygon in greenland")).to_be_visible()
|
||||
expect(markers).to_have_count(0) # Hidden by filter
|
||||
expect(paths).to_have_count(1) # Only polygon
|
||||
# Empty the filter
|
||||
filter_.fill("")
|
||||
filter_.blur()
|
||||
expect(markers).to_have_count(1)
|
||||
expect(paths).to_have_count(2)
|
||||
filter_.type("point")
|
||||
expect(page.get_by_text("one point in france")).to_be_visible()
|
||||
expect(page.get_by_text("one line in new zeland")).to_be_hidden()
|
||||
expect(page.get_by_text("one polygon in greenland")).to_be_hidden()
|
||||
expect(markers).to_have_count(1)
|
||||
expect(paths).to_have_count(0)
|
||||
|
||||
|
||||
def test_data_browser_can_show_only_visible_features(live_server, page, bootstrap, map):
|
||||
|
@ -131,3 +145,25 @@ def test_data_browser_bbox_limit_should_be_dynamic(live_server, page, bootstrap,
|
|||
expect(page.get_by_text("one point in france")).to_be_visible()
|
||||
expect(page.get_by_text("one polygon in greenland")).to_be_visible()
|
||||
expect(page.get_by_text("one line in new zeland")).to_be_hidden()
|
||||
|
||||
|
||||
def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map):
|
||||
# Include a variable
|
||||
map.settings["properties"]["labelKey"] = "{name} ({foo})"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.get_by_text("one point in france (point)")).to_be_visible()
|
||||
expect(page.get_by_text("one line in new zeland (line)")).to_be_visible()
|
||||
expect(page.get_by_text("one polygon in greenland (polygon)")).to_be_visible()
|
||||
filter_ = page.locator("input[name='filter']")
|
||||
expect(filter_).to_be_visible()
|
||||
filter_.type("foobar") # Hide all
|
||||
expect(page.get_by_text("one point in france (point)")).to_be_hidden()
|
||||
expect(page.get_by_text("one line in new zeland (line)")).to_be_hidden()
|
||||
expect(page.get_by_text("one polygon in greenland (polygon)")).to_be_hidden()
|
||||
# Empty back the filter
|
||||
filter_.fill("")
|
||||
filter_.blur()
|
||||
expect(page.get_by_text("one point in france (point)")).to_be_visible()
|
||||
expect(page.get_by_text("one line in new zeland (line)")).to_be_visible()
|
||||
expect(page.get_by_text("one polygon in greenland (polygon)")).to_be_visible()
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import re
|
||||
from time import sleep
|
||||
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from umap.models import DataLayer
|
||||
|
||||
from ..base import DataLayerFactory, MapFactory
|
||||
|
||||
DATALAYER_UPDATE = re.compile(r".*/datalayer/update/.*")
|
||||
|
||||
|
||||
def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
||||
# Let's create a new map with an empty datalayer
|
||||
|
@ -31,11 +36,17 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
|||
map_el_p1.click(position={"x": 200, "y": 200})
|
||||
expect(marker_pane_p1).to_have_count(1)
|
||||
|
||||
save_p1.click()
|
||||
with page_one.expect_response(DATALAYER_UPDATE):
|
||||
save_p1.click()
|
||||
# Prefent two layers to be saved on the same second, as we compare them based
|
||||
# on time in case of conflict. FIXME do not use time for comparison.
|
||||
sleep(1)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"editMode": "advanced",
|
||||
"inCaption": True,
|
||||
}
|
||||
|
||||
# Now navigate to this map from another tab
|
||||
|
@ -60,7 +71,9 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
|||
map_el_p2.click(position={"x": 220, "y": 220})
|
||||
expect(marker_pane_p2).to_have_count(2)
|
||||
|
||||
save_p2.click()
|
||||
with page_two.expect_response(DATALAYER_UPDATE):
|
||||
save_p2.click()
|
||||
sleep(1)
|
||||
# No change after the save
|
||||
expect(marker_pane_p2).to_have_count(2)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
|
@ -75,7 +88,8 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
|||
create_marker_p1.click()
|
||||
map_el_p1.click(position={"x": 150, "y": 150})
|
||||
expect(marker_pane_p1).to_have_count(2)
|
||||
save_p1.click()
|
||||
with page_one.expect_response(DATALAYER_UPDATE):
|
||||
save_p1.click()
|
||||
# Should now get the other marker too
|
||||
expect(marker_pane_p1).to_have_count(3)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
|
@ -92,7 +106,9 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
|||
create_marker_p1.click()
|
||||
map_el_p1.click(position={"x": 180, "y": 150})
|
||||
expect(marker_pane_p1).to_have_count(4)
|
||||
save_p1.click()
|
||||
with page_one.expect_response(DATALAYER_UPDATE):
|
||||
save_p1.click()
|
||||
sleep(1)
|
||||
# Should now get the other marker too
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
"browsable": True,
|
||||
|
@ -110,7 +126,9 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
|
|||
create_marker_p2.click()
|
||||
map_el_p2.click(position={"x": 250, "y": 150})
|
||||
expect(marker_pane_p2).to_have_count(3)
|
||||
save_p2.click()
|
||||
with page_two.expect_response(DATALAYER_UPDATE):
|
||||
save_p2.click()
|
||||
sleep(1)
|
||||
# Should now get the other markers too
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
"browsable": True,
|
||||
|
|
|
@ -61,7 +61,7 @@ def test_umap_export(map, live_server, datalayer, page):
|
|||
"attribution": "© OSM Contributors",
|
||||
"maxZoom": 18,
|
||||
"minZoom": 0,
|
||||
"url_template": "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
"url_template": "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
},
|
||||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
|
|
|
@ -11,8 +11,9 @@ def test_umap_import_from_file(live_server, datalayer, page):
|
|||
button = page.get_by_title("Import data")
|
||||
expect(button).to_be_visible()
|
||||
button.click()
|
||||
file_input = page.locator("input[type='file']")
|
||||
with page.expect_file_chooser() as fc_info:
|
||||
page.locator("input[type='file']").click()
|
||||
file_input.click()
|
||||
file_chooser = fc_info.value
|
||||
path = Path(__file__).parent.parent / "fixtures/display_on_load.umap"
|
||||
file_chooser.set_files(path)
|
||||
|
@ -23,6 +24,10 @@ def test_umap_import_from_file(live_server, datalayer, page):
|
|||
expect(layers).to_have_count(3)
|
||||
nonloaded = page.locator(".umap-browse-datalayers li.off")
|
||||
expect(nonloaded).to_have_count(1)
|
||||
assert file_input.input_value()
|
||||
# Close the import panel
|
||||
page.keyboard.press("Escape")
|
||||
assert not file_input.input_value()
|
||||
|
||||
|
||||
def test_umap_import_geojson_from_textarea(live_server, datalayer, page):
|
||||
|
|
|
@ -12,6 +12,35 @@ from ..base import DataLayerFactory
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_preconnect_for_tilelayer(map, page, live_server, tilelayer):
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
meta = page.locator('link[rel="preconnect"]')
|
||||
expect(meta).to_have_count(1)
|
||||
expect(meta).to_have_attribute("href", "//a.tile.openstreetmap.fr")
|
||||
# Add custom tilelayer
|
||||
map.settings["properties"]["tilelayer"] = {
|
||||
"name": "OSM Piano FR",
|
||||
"maxZoom": 20,
|
||||
"minZoom": 0,
|
||||
"attribution": "test",
|
||||
"url_template": "https://a.piano.tiles.quaidorsay.fr/fr{r}/{z}/{x}/{y}.png",
|
||||
}
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(meta).to_have_attribute("href", "//a.piano.tiles.quaidorsay.fr")
|
||||
# Add custom tilelayer with variable in domain, should create a preconnect
|
||||
map.settings["properties"]["tilelayer"] = {
|
||||
"name": "OSM Piano FR",
|
||||
"maxZoom": 20,
|
||||
"minZoom": 0,
|
||||
"attribution": "test",
|
||||
"url_template": "https://{s}.piano.tiles.quaidorsay.fr/fr{r}/{z}/{x}/{y}.png",
|
||||
}
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(meta).to_have_count(0)
|
||||
|
||||
|
||||
def test_default_view_latest_without_datalayer_should_use_default_center(
|
||||
map, live_server, datalayer, page
|
||||
):
|
||||
|
|
|
@ -46,7 +46,7 @@ def test_can_change_picto_at_map_level(map, live_server, page, pictos):
|
|||
marker = page.locator(".umap-div-icon img")
|
||||
expect(marker).to_have_count(1)
|
||||
# Should have default img
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.svg")
|
||||
edit_settings = page.get_by_title("Edit map properties")
|
||||
expect(edit_settings).to_be_visible()
|
||||
edit_settings.click()
|
||||
|
@ -66,7 +66,7 @@ def test_can_change_picto_at_map_level(map, live_server, page, pictos):
|
|||
symbols.click()
|
||||
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||
undefine.click()
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.svg")
|
||||
|
||||
|
||||
def test_can_change_picto_at_datalayer_level(map, live_server, page, pictos):
|
||||
|
@ -147,7 +147,7 @@ def test_can_use_remote_url_as_picto(map, live_server, page, pictos):
|
|||
marker = page.locator(".umap-div-icon img")
|
||||
expect(marker).to_have_count(1)
|
||||
# Should have default img
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||
expect(marker).to_have_attribute("src", "/static/umap/img/marker.svg")
|
||||
edit_settings = page.get_by_title("Edit map properties")
|
||||
expect(edit_settings).to_be_visible()
|
||||
edit_settings.click()
|
||||
|
|
39
umap/tests/integration/test_querystring.py
Normal file
39
umap/tests/integration/test_querystring.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_scale_control(map, live_server, datalayer, page):
|
||||
control = page.locator(".leaflet-control-scale")
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(control).to_be_visible()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?scaleControl=false")
|
||||
expect(control).to_be_hidden()
|
||||
|
||||
|
||||
def test_datalayers_control(map, live_server, datalayer, page):
|
||||
control = page.locator(".umap-browse-toggle")
|
||||
box = page.locator(".umap-browse-datalayers")
|
||||
more = page.get_by_title("More controls")
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(control).to_be_visible()
|
||||
expect(box).to_be_hidden()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?datalayersControl=true")
|
||||
expect(control).to_be_visible()
|
||||
expect(box).to_be_hidden()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?datalayersControl=null")
|
||||
expect(control).to_be_hidden()
|
||||
expect(more).to_be_visible()
|
||||
more.click()
|
||||
expect(control).to_be_visible()
|
||||
expect(box).to_be_hidden()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?datalayersControl=false")
|
||||
expect(control).to_be_hidden()
|
||||
expect(more).to_be_visible()
|
||||
more.click()
|
||||
expect(control).to_be_hidden()
|
||||
expect(box).to_be_hidden()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?datalayersControl=expanded")
|
||||
expect(control).to_be_hidden()
|
||||
expect(box).to_be_visible()
|
|
@ -19,7 +19,7 @@ def post_data():
|
|||
return {
|
||||
"name": "name",
|
||||
"center": '{"type":"Point","coordinates":[13.447265624999998,48.94415123418794]}', # noqa
|
||||
"settings": '{"type":"Feature","geometry":{"type":"Point","coordinates":[5.0592041015625,52.05924589011585]},"properties":{"tilelayer":{"maxZoom":20,"url_template":"http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png","minZoom":0,"attribution":"HOT and friends"},"licence":"","description":"","name":"test enrhûmé","tilelayersControl":true,"displayDataBrowserOnLoad":false,"displayPopupFooter":true,"displayCaptionOnLoad":false,"miniMap":true,"moreControl":true,"scaleControl":true,"zoomControl":true,"datalayersControl":true,"zoom":8}}', # noqa
|
||||
"settings": '{"type":"Feature","geometry":{"type":"Point","coordinates":[5.0592041015625,52.05924589011585]},"properties":{"tilelayer":{"maxZoom":20,"url_template":"http://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png","minZoom":0,"attribution":"HOT and friends"},"licence":"","description":"","name":"test enrhûmé","tilelayersControl":true,"displayDataBrowserOnLoad":false,"displayPopupFooter":true,"displayCaptionOnLoad":false,"miniMap":true,"moreControl":true,"scaleControl":true,"zoomControl":true,"datalayersControl":true,"zoom":8}}', # noqa
|
||||
}
|
||||
|
||||
|
||||
|
@ -275,7 +275,7 @@ def test_owner_cannot_access_map_with_share_status_blocked(client, map):
|
|||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_non_editor_cannot_access_map_if_share_status_private(client, map, user): # noqa
|
||||
def test_non_editor_cannot_access_map_if_share_status_private(client, map, user):
|
||||
url = reverse("map", args=(map.slug, map.pk))
|
||||
map.share_status = map.PRIVATE
|
||||
map.save()
|
||||
|
@ -346,14 +346,14 @@ def test_anonymous_create(cookieclient, post_data):
|
|||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_update_without_cookie_fails(client, anonymap, post_data): # noqa
|
||||
def test_anonymous_update_without_cookie_fails(client, anonymap, post_data):
|
||||
url = reverse("map_update", kwargs={"map_id": anonymap.pk})
|
||||
response = client.post(url, post_data)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_anonymous_update_with_cookie_should_work(cookieclient, anonymap, post_data): # noqa
|
||||
def test_anonymous_update_with_cookie_should_work(cookieclient, anonymap, post_data):
|
||||
url = reverse("map_update", kwargs={"map_id": anonymap.pk})
|
||||
# POST only mendatory fields
|
||||
name = "new map name"
|
||||
|
@ -420,7 +420,7 @@ def test_bad_anonymous_edit_url_should_return_403(cookieclient, anonymap):
|
|||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed(
|
||||
client, anonymap, user
|
||||
): # noqa
|
||||
):
|
||||
assert Map.objects.count() == 1
|
||||
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
||||
anonymap.edit_status = anonymap.OWNER
|
||||
|
@ -434,7 +434,7 @@ def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed(
|
|||
|
||||
|
||||
@pytest.mark.usefixtures("allow_anonymous")
|
||||
def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonymap): # noqa
|
||||
def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonymap):
|
||||
assert Map.objects.count() == 1
|
||||
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
||||
anonymap.edit_status = anonymap.ANONYMOUS
|
||||
|
@ -624,7 +624,7 @@ def test_download(client, map, datalayer):
|
|||
"attribution": "© OSM Contributors",
|
||||
"maxZoom": 18,
|
||||
"minZoom": 0,
|
||||
"url_template": "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
"url_template": "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
},
|
||||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
|
@ -675,3 +675,63 @@ def test_download_my_map(client, map, datalayer):
|
|||
# Test response is a json
|
||||
j = json.loads(response.content.decode())
|
||||
assert j["type"] == "umap"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("share_status", [Map.PRIVATE, Map.BLOCKED, Map.OPEN])
|
||||
def test_oembed_shared_status_map(client, map, datalayer, share_status):
|
||||
map.share_status = share_status
|
||||
map.save()
|
||||
url = f"{reverse('map_oembed')}?url=http://testserver{map.get_absolute_url()}"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_oembed_no_url_map(client, map, datalayer):
|
||||
url = reverse("map_oembed")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_oembed_wrong_format_map(client, map, datalayer):
|
||||
url = (
|
||||
f"{reverse('map_oembed')}"
|
||||
f"?url=http://testserver{map.get_absolute_url()}&format=xml"
|
||||
)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 501
|
||||
|
||||
|
||||
def test_oembed_wrong_domain_map(client, map, datalayer):
|
||||
url = f"{reverse('map_oembed')}?url=http://BADserver{map.get_absolute_url()}"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_oembed_map(client, map, datalayer):
|
||||
url = f"{reverse('map_oembed')}?url=http://testserver{map.get_absolute_url()}"
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
j = json.loads(response.content.decode())
|
||||
assert j["type"] == "rich"
|
||||
assert j["version"] == "1.0"
|
||||
assert j["width"] == 800
|
||||
assert j["height"] == 300
|
||||
assert j["html"] == (
|
||||
'<iframe width="100%" height="300px" frameborder="0" allowfullscreen '
|
||||
f'allow="geolocation" src="//testserver/en/map/test-map_{map.id}"></iframe>'
|
||||
f'<p><a href="//testserver/en/map/test-map_{map.id}">See full screen</a></p>'
|
||||
)
|
||||
|
||||
|
||||
def test_oembed_link(client, map, datalayer):
|
||||
response = client.get(map.get_absolute_url())
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
'<link rel="alternate" type="application/json+oembed"'
|
||||
in response.content.decode()
|
||||
)
|
||||
assert (
|
||||
'href="http://testserver/map/oembed/'
|
||||
f'?url=http%3A//testserver/en/map/test-map_{map.id}&format=json"'
|
||||
) in response.content.decode()
|
||||
assert 'title="test map oEmbed URL" />' in response.content.decode()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
import socket
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
|
@ -10,6 +10,7 @@ from django.urls import reverse
|
|||
from django.utils.timezone import make_aware
|
||||
|
||||
from umap import VERSION
|
||||
from umap.models import Map, Star
|
||||
from umap.views import validate_url
|
||||
|
||||
from .base import MapFactory, UserFactory
|
||||
|
@ -391,3 +392,51 @@ def test_webmanifest(client):
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_home_feed(client, settings, user, tilelayer):
|
||||
settings.UMAP_HOME_FEED = "latest"
|
||||
staff = UserFactory(username="Staff", is_staff=True)
|
||||
starred = MapFactory(
|
||||
owner=user, name="A public map starred by staff", share_status=Map.PUBLIC
|
||||
)
|
||||
MapFactory(
|
||||
owner=user, name="A public map not starred by staff", share_status=Map.PUBLIC
|
||||
)
|
||||
non_staff = MapFactory(
|
||||
owner=user, name="A public map starred by non staff", share_status=Map.PUBLIC
|
||||
)
|
||||
private = MapFactory(
|
||||
owner=user, name="A private map starred by staff", share_status=Map.PRIVATE
|
||||
)
|
||||
reserved = MapFactory(
|
||||
owner=user, name="A reserved map starred by staff", share_status=Map.OPEN
|
||||
)
|
||||
Star.objects.create(by=staff, map=starred)
|
||||
Star.objects.create(by=staff, map=private)
|
||||
Star.objects.create(by=staff, map=reserved)
|
||||
Star.objects.create(by=user, map=non_staff)
|
||||
response = client.get(reverse("home"))
|
||||
content = response.content.decode()
|
||||
assert "A public map starred by staff" in content
|
||||
assert "A public map not starred by staff" in content
|
||||
assert "A public map starred by non staff" in content
|
||||
assert "A private map starred by staff" not in content
|
||||
assert "A reserved map starred by staff" not in content
|
||||
settings.UMAP_HOME_FEED = "highlighted"
|
||||
response = client.get(reverse("home"))
|
||||
content = response.content.decode()
|
||||
assert "A public map starred by staff" in content
|
||||
assert "A public map not starred by staff" not in content
|
||||
assert "A public map starred by non staff" not in content
|
||||
assert "A private map starred by staff" not in content
|
||||
assert "A reserved map starred by staff" not in content
|
||||
settings.UMAP_HOME_FEED = None
|
||||
response = client.get(reverse("home"))
|
||||
content = response.content.decode()
|
||||
assert "A public map starred by staff" not in content
|
||||
assert "A public map not starred by staff" not in content
|
||||
assert "A public map starred by non staff" not in content
|
||||
assert "A private map starred by staff" not in content
|
||||
assert "A reserved map starred by staff" not in content
|
||||
|
|
|
@ -41,6 +41,7 @@ urlpatterns = [
|
|||
),
|
||||
re_path(r"^i18n/", include("django.conf.urls.i18n")),
|
||||
re_path(r"^agnocomplete/", include("agnocomplete.urls")),
|
||||
re_path(r"^map/oembed/", views.MapOEmbed.as_view(), name="map_oembed"),
|
||||
re_path(
|
||||
r"^map/(?P<map_id>\d+)/download/",
|
||||
can_view_map(views.MapDownload.as_view()),
|
||||
|
|
|
@ -18,20 +18,23 @@ from django.contrib.auth import logout as do_logout
|
|||
from django.contrib.gis.measure import D
|
||||
from django.contrib.postgres.search import SearchQuery, SearchVector
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.mail import send_mail
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||
from django.core.signing import BadSignature, Signer
|
||||
from django.core.validators import URLValidator, ValidationError
|
||||
from django.http import (
|
||||
Http404,
|
||||
HttpResponse,
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseForbidden,
|
||||
HttpResponsePermanentRedirect,
|
||||
HttpResponseRedirect,
|
||||
HttpResponseServerError,
|
||||
)
|
||||
from django.middleware.gzip import re_accepts_gzip
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.urls import resolve, reverse, reverse_lazy
|
||||
from django.utils.encoding import smart_bytes
|
||||
from django.utils.http import http_date
|
||||
from django.utils.timezone import make_aware
|
||||
|
@ -118,13 +121,26 @@ class PublicMapsMixin(object):
|
|||
maps = qs.order_by("-modified_at")
|
||||
return maps
|
||||
|
||||
def get_highlighted_maps(self):
|
||||
staff = User.objects.filter(is_staff=True)
|
||||
stars = Star.objects.filter(by__in=staff).values("map")
|
||||
qs = Map.public.filter(pk__in=stars)
|
||||
maps = qs.order_by("-modified_at")
|
||||
return maps
|
||||
|
||||
|
||||
class Home(PaginatorMixin, TemplateView, PublicMapsMixin):
|
||||
template_name = "umap/home.html"
|
||||
list_template_name = "umap/map_list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
maps = self.get_public_maps()
|
||||
if settings.UMAP_HOME_FEED is None:
|
||||
maps = []
|
||||
elif settings.UMAP_HOME_FEED == "highlighted":
|
||||
maps = self.get_highlighted_maps()
|
||||
else:
|
||||
maps = self.get_public_maps()
|
||||
maps = self.paginate(maps, settings.UMAP_MAPS_PER_PAGE)
|
||||
|
||||
demo_map = None
|
||||
if hasattr(settings, "UMAP_DEMO_PK"):
|
||||
|
@ -140,8 +156,6 @@ class Home(PaginatorMixin, TemplateView, PublicMapsMixin):
|
|||
except Map.DoesNotExist:
|
||||
pass
|
||||
|
||||
maps = self.paginate(maps, settings.UMAP_MAPS_PER_PAGE)
|
||||
|
||||
return {
|
||||
"maps": maps,
|
||||
"demo_map": demo_map,
|
||||
|
@ -421,6 +435,21 @@ class MapDetailMixin:
|
|||
model = Map
|
||||
pk_url_kwarg = "map_id"
|
||||
|
||||
def set_preconnect(self, properties, context):
|
||||
# Try to extract the tilelayer domain, in order to but a preconnect meta.
|
||||
url_template = properties.get("tilelayer", {}).get("url_template")
|
||||
# Not explicit tilelayer set, take the first of the list, which will be
|
||||
# used by frontend too.
|
||||
if not url_template:
|
||||
tilelayers = properties.get("tilelayers")
|
||||
if tilelayers:
|
||||
url_template = tilelayers[0].get("url_template")
|
||||
if url_template:
|
||||
domain = urlparse(url_template).netloc
|
||||
# Do not try to preconnect on domains with variables
|
||||
if domain and "{" not in domain:
|
||||
context["preconnect_domains"] = [f"//{domain}"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user = self.request.user
|
||||
|
@ -428,7 +457,7 @@ class MapDetailMixin:
|
|||
"urls": _urls_for_js(),
|
||||
"tilelayers": TileLayer.get_list(),
|
||||
"editMode": self.edit_mode,
|
||||
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
|
||||
"default_iconUrl": "%sumap/img/marker.svg" % 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()),
|
||||
|
@ -473,6 +502,7 @@ class MapDetailMixin:
|
|||
map_settings["properties"].update(properties)
|
||||
map_settings["properties"]["datalayers"] = self.get_datalayers()
|
||||
context["map_settings"] = json.dumps(map_settings, indent=settings.DEBUG)
|
||||
self.set_preconnect(map_settings["properties"], context)
|
||||
return context
|
||||
|
||||
def get_datalayers(self):
|
||||
|
@ -525,6 +555,16 @@ class PermissionsMixin:
|
|||
|
||||
|
||||
class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["oembed_absolute_uri"] = self.request.build_absolute_uri(
|
||||
reverse("map_oembed")
|
||||
)
|
||||
context["absolute_uri"] = self.request.build_absolute_uri(
|
||||
self.object.get_absolute_url()
|
||||
)
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
canonical = self.get_canonical_url()
|
||||
|
@ -606,6 +646,52 @@ class MapDownload(DetailView):
|
|||
return response
|
||||
|
||||
|
||||
class MapOEmbed(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
data = {"type": "rich", "version": "1.0"}
|
||||
format_ = request.GET.get("format", "json")
|
||||
if format_ != "json":
|
||||
response = HttpResponseServerError("Only `json` format is implemented.")
|
||||
response.status_code = 501
|
||||
return response
|
||||
|
||||
url = request.GET.get("url")
|
||||
if not url:
|
||||
raise Http404("Missing `url` parameter.")
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
netloc = parsed_url.netloc
|
||||
allowed_hosts = settings.ALLOWED_HOSTS
|
||||
if parsed_url.hostname not in allowed_hosts and allowed_hosts != ["*"]:
|
||||
raise Http404("Host not allowed.")
|
||||
|
||||
url_path = parsed_url.path
|
||||
view, args, kwargs = resolve(url_path)
|
||||
if "slug" not in kwargs or "map_id" not in kwargs:
|
||||
raise Http404("Invalid URL path.")
|
||||
|
||||
map_ = Map.objects.get(id=kwargs["map_id"], slug=kwargs["slug"])
|
||||
|
||||
if map_.share_status != Map.PUBLIC:
|
||||
raise PermissionDenied("This map is not public.")
|
||||
|
||||
map_url = map_.get_absolute_url()
|
||||
label = _("See full screen")
|
||||
height = 300
|
||||
data["height"] = height
|
||||
width = 800
|
||||
data["width"] = width
|
||||
# TODISCUSS: do we keep width=100% by default for the iframe?
|
||||
html = (
|
||||
f'<iframe width="100%" height="{height}px" '
|
||||
f'frameborder="0" allowfullscreen allow="geolocation" '
|
||||
f'src="//{netloc}{map_url}"></iframe>'
|
||||
f'<p><a href="//{netloc}{map_url}">{label}</a></p>'
|
||||
)
|
||||
data["html"] = html
|
||||
return simple_json_response(**data)
|
||||
|
||||
|
||||
class MapViewGeoJSON(MapView):
|
||||
def get_canonical_url(self):
|
||||
return reverse("map_geojson", args=(self.object.pk,))
|
||||
|
|
Loading…
Reference in a new issue