parent
b75e3bedd7
commit
df76ffd80e
5 changed files with 130 additions and 8 deletions
|
@ -12,6 +12,9 @@
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% umap_js locale=locale %}
|
{% umap_js locale=locale %}
|
||||||
{% if object.share_status != object.PUBLIC %}<meta name="robots" content="noindex">{% endif %}
|
{% 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 %}
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% block map_init %}
|
{% block map_init %}
|
||||||
|
|
|
@ -275,7 +275,7 @@ def test_owner_cannot_access_map_with_share_status_blocked(client, map):
|
||||||
assert response.status_code == 403
|
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))
|
url = reverse("map", args=(map.slug, map.pk))
|
||||||
map.share_status = map.PRIVATE
|
map.share_status = map.PRIVATE
|
||||||
map.save()
|
map.save()
|
||||||
|
@ -346,14 +346,14 @@ def test_anonymous_create(cookieclient, post_data):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("allow_anonymous")
|
@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})
|
url = reverse("map_update", kwargs={"map_id": anonymap.pk})
|
||||||
response = client.post(url, post_data)
|
response = client.post(url, post_data)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("allow_anonymous")
|
@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})
|
url = reverse("map_update", kwargs={"map_id": anonymap.pk})
|
||||||
# POST only mendatory fields
|
# POST only mendatory fields
|
||||||
name = "new map name"
|
name = "new map name"
|
||||||
|
@ -420,7 +420,7 @@ def test_bad_anonymous_edit_url_should_return_403(cookieclient, anonymap):
|
||||||
@pytest.mark.usefixtures("allow_anonymous")
|
@pytest.mark.usefixtures("allow_anonymous")
|
||||||
def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed(
|
def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed(
|
||||||
client, anonymap, user
|
client, anonymap, user
|
||||||
): # noqa
|
):
|
||||||
assert Map.objects.count() == 1
|
assert Map.objects.count() == 1
|
||||||
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
||||||
anonymap.edit_status = anonymap.OWNER
|
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")
|
@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
|
assert Map.objects.count() == 1
|
||||||
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
|
||||||
anonymap.edit_status = anonymap.ANONYMOUS
|
anonymap.edit_status = anonymap.ANONYMOUS
|
||||||
|
@ -675,3 +675,63 @@ def test_download_my_map(client, map, datalayer):
|
||||||
# Test response is a json
|
# Test response is a json
|
||||||
j = json.loads(response.content.decode())
|
j = json.loads(response.content.decode())
|
||||||
assert j["type"] == "umap"
|
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 json
|
||||||
import socket
|
import socket
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -41,6 +41,7 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
re_path(r"^i18n/", include("django.conf.urls.i18n")),
|
re_path(r"^i18n/", include("django.conf.urls.i18n")),
|
||||||
re_path(r"^agnocomplete/", include("agnocomplete.urls")),
|
re_path(r"^agnocomplete/", include("agnocomplete.urls")),
|
||||||
|
re_path(r"^map/oembed/", views.MapOEmbed.as_view(), name="map_oembed"),
|
||||||
re_path(
|
re_path(
|
||||||
r"^map/(?P<map_id>\d+)/download/",
|
r"^map/(?P<map_id>\d+)/download/",
|
||||||
can_view_map(views.MapDownload.as_view()),
|
can_view_map(views.MapDownload.as_view()),
|
||||||
|
|
|
@ -18,21 +18,23 @@ from django.contrib.auth import logout as do_logout
|
||||||
from django.contrib.gis.measure import D
|
from django.contrib.gis.measure import D
|
||||||
from django.contrib.postgres.search import SearchQuery, SearchVector
|
from django.contrib.postgres.search import SearchQuery, SearchVector
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||||
from django.core.signing import BadSignature, Signer
|
from django.core.signing import BadSignature, Signer
|
||||||
from django.core.validators import URLValidator, ValidationError
|
from django.core.validators import URLValidator, ValidationError
|
||||||
from django.db.models import Q
|
|
||||||
from django.http import (
|
from django.http import (
|
||||||
|
Http404,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
HttpResponseBadRequest,
|
HttpResponseBadRequest,
|
||||||
HttpResponseForbidden,
|
HttpResponseForbidden,
|
||||||
HttpResponsePermanentRedirect,
|
HttpResponsePermanentRedirect,
|
||||||
HttpResponseRedirect,
|
HttpResponseRedirect,
|
||||||
|
HttpResponseServerError,
|
||||||
)
|
)
|
||||||
from django.middleware.gzip import re_accepts_gzip
|
from django.middleware.gzip import re_accepts_gzip
|
||||||
from django.shortcuts import get_object_or_404
|
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.encoding import smart_bytes
|
||||||
from django.utils.http import http_date
|
from django.utils.http import http_date
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
|
@ -526,6 +528,16 @@ class PermissionsMixin:
|
||||||
|
|
||||||
|
|
||||||
class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
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):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
canonical = self.get_canonical_url()
|
canonical = self.get_canonical_url()
|
||||||
|
@ -607,6 +619,52 @@ class MapDownload(DetailView):
|
||||||
return response
|
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):
|
class MapViewGeoJSON(MapView):
|
||||||
def get_canonical_url(self):
|
def get_canonical_url(self):
|
||||||
return reverse("map_geojson", args=(self.object.pk,))
|
return reverse("map_geojson", args=(self.object.pk,))
|
||||||
|
|
Loading…
Reference in a new issue