2015-08-02 11:08:31 -05:00
|
|
|
|
import json
|
2023-02-27 05:04:09 -06:00
|
|
|
|
import mimetypes
|
2018-05-19 04:12:19 -05:00
|
|
|
|
import os
|
2017-05-10 04:12:10 -05:00
|
|
|
|
import re
|
2014-04-19 10:54:51 -05:00
|
|
|
|
import socket
|
2023-11-23 17:34:24 -06:00
|
|
|
|
from datetime import datetime, timedelta
|
2023-06-02 15:45:13 -05:00
|
|
|
|
from http.client import InvalidURL
|
2020-03-25 02:56:43 -05:00
|
|
|
|
from io import BytesIO
|
2023-02-27 05:04:09 -06:00
|
|
|
|
from pathlib import Path
|
2023-11-23 18:50:00 -06:00
|
|
|
|
from urllib.error import HTTPError, URLError
|
|
|
|
|
from urllib.parse import quote, urlparse
|
|
|
|
|
from urllib.request import Request, build_opener
|
2014-04-19 10:54:51 -05:00
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.contrib import messages
|
|
|
|
|
from django.contrib.auth import get_user_model
|
2023-11-23 18:50:00 -06:00
|
|
|
|
from django.contrib.auth import logout as do_logout
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.contrib.gis.measure import D
|
2023-05-22 16:47:04 -05:00
|
|
|
|
from django.contrib.postgres.search import SearchQuery, SearchVector
|
2023-11-14 21:50:25 -06:00
|
|
|
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
2023-05-31 10:05:57 -05:00
|
|
|
|
from django.core.mail import send_mail
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|
|
|
|
from django.core.signing import BadSignature, Signer
|
|
|
|
|
from django.core.validators import URLValidator, ValidationError
|
|
|
|
|
from django.db.models import Q
|
2023-02-27 04:38:59 -06:00
|
|
|
|
from django.http import (
|
|
|
|
|
HttpResponse,
|
|
|
|
|
HttpResponseBadRequest,
|
|
|
|
|
HttpResponseForbidden,
|
|
|
|
|
HttpResponsePermanentRedirect,
|
|
|
|
|
HttpResponseRedirect,
|
|
|
|
|
)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.middleware.gzip import re_accepts_gzip
|
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
|
from django.urls import reverse, reverse_lazy
|
2023-03-01 12:13:45 -06:00
|
|
|
|
from django.utils.encoding import smart_bytes
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.utils.http import http_date
|
2023-11-23 17:34:24 -06:00
|
|
|
|
from django.utils.timezone import make_aware
|
2021-05-17 03:51:24 -05:00
|
|
|
|
from django.utils.translation import gettext as _
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.utils.translation import to_locale
|
2023-11-10 10:26:05 -06:00
|
|
|
|
from django.views.decorators.cache import cache_control
|
|
|
|
|
from django.views.decorators.http import require_GET
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.views.generic import DetailView, TemplateView, View
|
|
|
|
|
from django.views.generic.base import RedirectView
|
|
|
|
|
from django.views.generic.detail import BaseDetailView
|
2023-05-31 10:05:57 -05:00
|
|
|
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
2018-05-19 04:12:19 -05:00
|
|
|
|
from django.views.generic.list import ListView
|
|
|
|
|
|
2023-06-06 13:05:35 -05:00
|
|
|
|
from . import VERSION
|
2023-02-27 04:38:59 -06:00
|
|
|
|
from .forms import (
|
2023-11-23 18:50:00 -06:00
|
|
|
|
DEFAULT_CENTER,
|
2023-02-27 04:38:59 -06:00
|
|
|
|
DEFAULT_LATITUDE,
|
|
|
|
|
DEFAULT_LONGITUDE,
|
2023-09-07 03:31:25 -05:00
|
|
|
|
AnonymousDataLayerPermissionsForm,
|
2023-09-18 13:05:17 -05:00
|
|
|
|
AnonymousMapPermissionsForm,
|
2023-11-23 18:50:00 -06:00
|
|
|
|
DataLayerForm,
|
|
|
|
|
DataLayerPermissionsForm,
|
2023-02-27 04:38:59 -06:00
|
|
|
|
FlatErrorList,
|
|
|
|
|
MapSettingsForm,
|
2023-05-31 10:05:57 -05:00
|
|
|
|
SendLinkForm,
|
2023-02-27 04:38:59 -06:00
|
|
|
|
UpdateMapPermissionsForm,
|
2023-08-21 08:09:30 -05:00
|
|
|
|
UserProfileForm,
|
2023-02-27 04:38:59 -06:00
|
|
|
|
)
|
2019-04-09 02:42:09 -05:00
|
|
|
|
from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
|
2020-03-25 02:56:43 -05:00
|
|
|
|
from .utils import ConflictError, get_uri_template, gzip_file, is_ajax, merge_features
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2014-06-19 07:39:45 -05:00
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
2012-11-20 03:47:19 -06:00
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
PRIVATE_IP = re.compile(
|
|
|
|
|
r"((^127\.)|(^10\.)"
|
|
|
|
|
r"|(^172\.1[6-9]\.)"
|
|
|
|
|
r"|(^172\.2[0-9]\.)"
|
|
|
|
|
r"|(^172\.3[0-1]\.)"
|
|
|
|
|
r"|(^192\.168\.))"
|
|
|
|
|
)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
ANONYMOUS_COOKIE_MAX_AGE = 60 * 60 * 24 * 30 # One month
|
2017-05-10 04:12:10 -05:00
|
|
|
|
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
class PaginatorMixin:
|
2013-02-12 10:34:33 -06:00
|
|
|
|
per_page = 5
|
|
|
|
|
|
2015-10-11 15:19:51 -05:00
|
|
|
|
def paginate(self, qs, per_page=None):
|
|
|
|
|
paginator = Paginator(qs, per_page or self.per_page)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
page = self.request.GET.get("p")
|
2013-02-12 10:34:33 -06:00
|
|
|
|
try:
|
|
|
|
|
qs = paginator.page(page)
|
|
|
|
|
except PageNotAnInteger:
|
|
|
|
|
# If page is not an integer, deliver first page.
|
|
|
|
|
qs = paginator.page(1)
|
|
|
|
|
except EmptyPage:
|
2015-08-02 11:08:31 -05:00
|
|
|
|
# If page is out of range (e.g. 9999), deliver last page of
|
|
|
|
|
# results.
|
2013-02-12 10:34:33 -06:00
|
|
|
|
qs = paginator.page(paginator.num_pages)
|
|
|
|
|
return qs
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
kwargs.update({"is_ajax": is_ajax(self.request)})
|
|
|
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
|
|
def get_template_names(self):
|
|
|
|
|
"""
|
|
|
|
|
Dispatch template according to the kind of request: ajax or normal.
|
|
|
|
|
"""
|
|
|
|
|
if is_ajax(self.request):
|
|
|
|
|
return [self.list_template_name]
|
|
|
|
|
return super().get_template_names()
|
|
|
|
|
|
2013-02-12 10:34:33 -06:00
|
|
|
|
|
2023-06-30 20:40:35 -05:00
|
|
|
|
class PublicMapsMixin(object):
|
|
|
|
|
def get_public_maps(self):
|
2014-09-09 10:43:28 -05:00
|
|
|
|
qs = Map.public
|
2023-02-27 04:00:33 -06:00
|
|
|
|
if (
|
|
|
|
|
settings.UMAP_EXCLUDE_DEFAULT_MAPS
|
|
|
|
|
and "spatialite" not in settings.DATABASES["default"]["ENGINE"]
|
|
|
|
|
):
|
|
|
|
|
# Unsupported query type for sqlite.
|
2014-09-09 10:43:28 -05:00
|
|
|
|
qs = qs.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1)))
|
2023-06-30 20:40:35 -05:00
|
|
|
|
maps = qs.order_by("-modified_at")
|
|
|
|
|
return maps
|
|
|
|
|
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
class Home(PaginatorMixin, TemplateView, PublicMapsMixin):
|
2023-06-30 20:40:35 -05:00
|
|
|
|
template_name = "umap/home.html"
|
|
|
|
|
list_template_name = "umap/map_list.html"
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
maps = self.get_public_maps()
|
|
|
|
|
|
2013-02-12 10:34:33 -06:00
|
|
|
|
demo_map = None
|
|
|
|
|
if hasattr(settings, "UMAP_DEMO_PK"):
|
|
|
|
|
try:
|
2014-01-09 16:49:53 -06:00
|
|
|
|
demo_map = Map.public.get(pk=settings.UMAP_DEMO_PK)
|
2013-02-12 10:34:33 -06:00
|
|
|
|
except Map.DoesNotExist:
|
|
|
|
|
pass
|
2023-06-30 20:40:35 -05:00
|
|
|
|
|
2014-02-07 15:00:50 -06:00
|
|
|
|
showcase_map = None
|
|
|
|
|
if hasattr(settings, "UMAP_SHOWCASE_PK"):
|
|
|
|
|
try:
|
|
|
|
|
showcase_map = Map.public.get(pk=settings.UMAP_SHOWCASE_PK)
|
|
|
|
|
except Map.DoesNotExist:
|
|
|
|
|
pass
|
2023-06-30 20:40:35 -05:00
|
|
|
|
|
2016-01-02 06:42:57 -06:00
|
|
|
|
maps = self.paginate(maps, settings.UMAP_MAPS_PER_PAGE)
|
2013-02-12 10:34:33 -06:00
|
|
|
|
|
2012-11-20 03:47:19 -06:00
|
|
|
|
return {
|
2012-12-12 06:13:14 -06:00
|
|
|
|
"maps": maps,
|
2013-05-01 17:54:40 -05:00
|
|
|
|
"demo_map": demo_map,
|
2014-02-07 15:00:50 -06:00
|
|
|
|
"showcase_map": showcase_map,
|
2012-11-20 03:47:19 -06:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2012-11-20 03:47:19 -06:00
|
|
|
|
home = Home.as_view()
|
2012-12-11 13:04:03 -06:00
|
|
|
|
|
|
|
|
|
|
2013-02-12 10:34:33 -06:00
|
|
|
|
class About(Home):
|
|
|
|
|
template_name = "umap/about.html"
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2013-02-12 10:34:33 -06:00
|
|
|
|
about = About.as_view()
|
|
|
|
|
|
|
|
|
|
|
2023-08-21 08:09:30 -05:00
|
|
|
|
class UserProfile(UpdateView):
|
|
|
|
|
model = User
|
|
|
|
|
form_class = UserProfileForm
|
2023-08-21 10:08:57 -05:00
|
|
|
|
success_url = reverse_lazy("user_profile")
|
2023-08-21 08:09:30 -05:00
|
|
|
|
|
|
|
|
|
def get_object(self):
|
|
|
|
|
return self.get_queryset().get(pk=self.request.user.pk)
|
|
|
|
|
|
2023-08-21 10:08:57 -05:00
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
kwargs.update(
|
|
|
|
|
{"providers": self.object.social_auth.values_list("provider", flat=True)}
|
|
|
|
|
)
|
|
|
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
2023-08-21 08:09:30 -05:00
|
|
|
|
|
|
|
|
|
user_profile = UserProfile.as_view()
|
|
|
|
|
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
class UserMaps(PaginatorMixin, DetailView):
|
2012-12-11 13:04:03 -06:00
|
|
|
|
model = User
|
2023-06-16 07:59:59 -05:00
|
|
|
|
slug_url_kwarg = "identifier"
|
|
|
|
|
slug_field = settings.USER_URL_FIELD
|
2018-05-19 04:12:19 -05:00
|
|
|
|
list_template_name = "umap/map_list.html"
|
2012-12-11 13:04:03 -06:00
|
|
|
|
context_object_name = "current_user"
|
|
|
|
|
|
2023-05-15 07:50:18 -05:00
|
|
|
|
def is_owner(self):
|
|
|
|
|
return self.request.user == self.object
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def per_page(self):
|
|
|
|
|
if self.is_owner():
|
|
|
|
|
return settings.UMAP_MAPS_PER_PAGE_OWNER
|
|
|
|
|
return settings.UMAP_MAPS_PER_PAGE
|
|
|
|
|
|
|
|
|
|
def get_maps(self):
|
2023-11-18 13:04:08 -06:00
|
|
|
|
qs = Map.public
|
|
|
|
|
qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object))
|
|
|
|
|
return qs.order_by("-modified_at")
|
2023-05-15 07:50:18 -05:00
|
|
|
|
|
2012-12-11 13:04:03 -06:00
|
|
|
|
def get_context_data(self, **kwargs):
|
2023-05-15 07:50:18 -05:00
|
|
|
|
kwargs.update({"maps": self.paginate(self.get_maps(), self.per_page)})
|
|
|
|
|
return super().get_context_data(**kwargs)
|
2012-12-11 13:04:03 -06:00
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2012-12-11 13:04:03 -06:00
|
|
|
|
user_maps = UserMaps.as_view()
|
2012-12-16 08:10:00 -06:00
|
|
|
|
|
|
|
|
|
|
2019-04-09 02:42:09 -05:00
|
|
|
|
class UserStars(UserMaps):
|
|
|
|
|
template_name = "auth/user_stars.html"
|
|
|
|
|
|
2023-05-15 07:50:18 -05:00
|
|
|
|
def get_maps(self):
|
2019-04-09 02:42:09 -05:00
|
|
|
|
stars = Star.objects.filter(by=self.object).values("map")
|
2023-11-18 13:04:08 -06:00
|
|
|
|
qs = Map.public.filter(pk__in=stars)
|
2023-05-15 07:50:18 -05:00
|
|
|
|
return qs.order_by("-modified_at")
|
|
|
|
|
|
2019-04-09 02:42:09 -05:00
|
|
|
|
|
|
|
|
|
user_stars = UserStars.as_view()
|
|
|
|
|
|
|
|
|
|
|
2023-07-04 09:09:12 -05:00
|
|
|
|
class SearchMixin:
|
|
|
|
|
def get_search_queryset(self, **kwargs):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
q = self.request.GET.get("q")
|
2013-02-12 10:34:33 -06:00
|
|
|
|
if q:
|
2023-05-11 04:33:08 -05:00
|
|
|
|
vector = SearchVector("name", config=settings.UMAP_SEARCH_CONFIGURATION)
|
|
|
|
|
query = SearchQuery(
|
|
|
|
|
q, config=settings.UMAP_SEARCH_CONFIGURATION, search_type="websearch"
|
|
|
|
|
)
|
2023-07-04 09:09:12 -05:00
|
|
|
|
return Map.objects.annotate(search=vector).filter(search=query)
|
|
|
|
|
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
class Search(PaginatorMixin, TemplateView, PublicMapsMixin, SearchMixin):
|
2023-07-04 09:09:12 -05:00
|
|
|
|
template_name = "umap/search.html"
|
|
|
|
|
list_template_name = "umap/map_list.html"
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
qs = self.get_search_queryset()
|
|
|
|
|
qs_count = 0
|
|
|
|
|
results = []
|
2023-07-12 03:39:37 -05:00
|
|
|
|
if qs is not None:
|
2023-05-22 16:47:04 -05:00
|
|
|
|
qs = qs.filter(share_status=Map.PUBLIC).order_by("-modified_at")
|
2023-06-14 08:58:48 -05:00
|
|
|
|
qs_count = qs.count()
|
2023-05-11 04:33:08 -05:00
|
|
|
|
results = self.paginate(qs)
|
2023-06-30 20:40:35 -05:00
|
|
|
|
else:
|
2023-07-04 09:09:12 -05:00
|
|
|
|
results = self.get_public_maps()[: settings.UMAP_MAPS_PER_SEARCH]
|
|
|
|
|
kwargs.update({"maps": results, "count": qs_count})
|
2012-12-16 08:10:00 -06:00
|
|
|
|
return kwargs
|
|
|
|
|
|
2023-06-27 17:41:00 -05:00
|
|
|
|
@property
|
|
|
|
|
def per_page(self):
|
|
|
|
|
return settings.UMAP_MAPS_PER_SEARCH
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2012-12-16 08:10:00 -06:00
|
|
|
|
search = Search.as_view()
|
2014-02-07 15:00:50 -06:00
|
|
|
|
|
|
|
|
|
|
2023-07-12 03:27:22 -05:00
|
|
|
|
class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
|
2023-07-04 09:09:12 -05:00
|
|
|
|
model = User
|
|
|
|
|
template_name = "umap/user_dashboard.html"
|
2023-07-12 03:27:22 -05:00
|
|
|
|
list_template_name = "umap/map_table.html"
|
2023-07-04 09:09:12 -05:00
|
|
|
|
|
|
|
|
|
def get_object(self):
|
|
|
|
|
return self.get_queryset().get(pk=self.request.user.pk)
|
|
|
|
|
|
|
|
|
|
def get_maps(self):
|
|
|
|
|
qs = self.get_search_queryset() or Map.objects.all()
|
2023-09-22 12:00:42 -05:00
|
|
|
|
qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object))
|
2023-07-04 09:09:12 -05:00
|
|
|
|
return qs.order_by("-modified_at")
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
kwargs.update(
|
2023-12-27 12:26:31 -06:00
|
|
|
|
{
|
|
|
|
|
"q": self.request.GET.get("q"),
|
|
|
|
|
"maps": self.paginate(
|
|
|
|
|
self.get_maps(), settings.UMAP_MAPS_PER_PAGE_OWNER
|
|
|
|
|
),
|
|
|
|
|
}
|
2023-07-04 09:09:12 -05:00
|
|
|
|
)
|
|
|
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user_dashboard = UserDashboard.as_view()
|
|
|
|
|
|
|
|
|
|
|
2014-02-07 15:00:50 -06:00
|
|
|
|
class MapsShowCase(View):
|
2014-04-19 04:48:54 -05:00
|
|
|
|
def get(self, *args, **kwargs):
|
2015-08-02 11:08:31 -05:00
|
|
|
|
maps = Map.public.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1)))
|
2023-02-27 04:00:33 -06:00
|
|
|
|
maps = maps.order_by("-modified_at")[:2500]
|
2014-02-07 15:00:50 -06:00
|
|
|
|
|
|
|
|
|
def make(m):
|
2014-02-10 14:37:12 -06:00
|
|
|
|
description = m.description or ""
|
|
|
|
|
if m.owner:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
description = "{description}\n{by} [[{url}|{name}]]".format(
|
2014-02-10 14:37:12 -06:00
|
|
|
|
description=description,
|
|
|
|
|
by=_("by"),
|
2023-06-16 07:59:59 -05:00
|
|
|
|
url=m.owner.get_url(),
|
2014-02-10 14:37:12 -06:00
|
|
|
|
name=m.owner,
|
|
|
|
|
)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
description = "{}\n[[{}|{}]]".format(
|
|
|
|
|
description, m.get_absolute_url(), _("View the map")
|
|
|
|
|
)
|
|
|
|
|
geometry = m.settings.get("geometry", json.loads(m.center.geojson))
|
2014-02-07 15:00:50 -06:00
|
|
|
|
return {
|
|
|
|
|
"type": "Feature",
|
|
|
|
|
"geometry": geometry,
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"properties": {"name": m.name, "description": description},
|
2014-02-07 15:00:50 -06:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
geojson = {"type": "FeatureCollection", "features": [make(m) for m in maps]}
|
2015-10-17 06:09:58 -05:00
|
|
|
|
return HttpResponse(smart_bytes(json.dumps(geojson)))
|
2014-02-07 15:00:50 -06:00
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2014-02-07 15:00:50 -06:00
|
|
|
|
showcase = MapsShowCase.as_view()
|
2014-04-19 04:48:54 -05:00
|
|
|
|
|
|
|
|
|
|
2014-04-19 10:54:51 -05:00
|
|
|
|
def validate_url(request):
|
|
|
|
|
assert request.method == "GET"
|
2021-05-17 03:51:24 -05:00
|
|
|
|
assert is_ajax(request)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
url = request.GET.get("url")
|
2014-04-19 10:54:51 -05:00
|
|
|
|
assert url
|
|
|
|
|
try:
|
|
|
|
|
URLValidator(url)
|
|
|
|
|
except ValidationError:
|
|
|
|
|
raise AssertionError()
|
2023-02-27 04:00:33 -06:00
|
|
|
|
assert "HTTP_REFERER" in request.META
|
|
|
|
|
referer = urlparse(request.META.get("HTTP_REFERER"))
|
2014-04-19 10:54:51 -05:00
|
|
|
|
toproxy = urlparse(url)
|
|
|
|
|
local = urlparse(settings.SITE_URL)
|
|
|
|
|
assert toproxy.hostname
|
|
|
|
|
assert referer.hostname == local.hostname
|
|
|
|
|
assert toproxy.hostname != "localhost"
|
|
|
|
|
assert toproxy.netloc != local.netloc
|
2014-07-18 16:26:16 -05:00
|
|
|
|
try:
|
|
|
|
|
# clean this when in python 3.4
|
|
|
|
|
ipaddress = socket.gethostbyname(toproxy.hostname)
|
|
|
|
|
except:
|
|
|
|
|
raise AssertionError()
|
2017-05-10 04:12:10 -05:00
|
|
|
|
assert not PRIVATE_IP.match(ipaddress)
|
2014-04-19 10:54:51 -05:00
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
|
2014-04-19 04:48:54 -05:00
|
|
|
|
class AjaxProxy(View):
|
|
|
|
|
def get(self, *args, **kwargs):
|
2014-04-19 10:54:51 -05:00
|
|
|
|
try:
|
|
|
|
|
url = validate_url(self.request)
|
2018-08-04 13:50:03 -05:00
|
|
|
|
except AssertionError:
|
2014-04-19 10:54:51 -05:00
|
|
|
|
return HttpResponseBadRequest()
|
Use X-Accel-Redirect for serving ajax-proxy request
uMap allows to use remote URL as data sources, but those URLs
are not always CORS open, so this is why there is this "ajax-proxy"
feature, where the URL is passed to the backend.
Additionally, there is a caching feature, which duration is configurable
through frontend settings. Valid values are: disabled, 5 min, 1 hour,
1 day.
Initially, I wanted this to be totally handled by Nginx, but I never
found a wayt to set the proxy_cache_valid value from a query string.
Since then, at least in OSM France servers, the ajax-proxy is still
handled by a Django view, which then opens the remote URL and transfert
the data. This is not optimal. And I suppose this is what is causing
hicups on the OSM France servers lately.
This PR provides a mix option, where python deals with validating the
URL and parsing the TTL parameter, and then it passes the hand to
nginx which will serve the remote content.
So, roughtly:
- the client calls /ajax-proxy/?url=xxx&ttl=300
- python will validate the URL (not internal calls…)
- if UMAP_SENDFILE_HEADER is set, then the python returns an empty
response with the path /proxy/http://url plus it will set the
cache ttl through the header X-Accel-Expires
- this /proxy/ location is then handled by nginx
2023-08-24 06:09:23 -05:00
|
|
|
|
try:
|
|
|
|
|
ttl = int(self.request.GET.get("ttl"))
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
ttl = None
|
|
|
|
|
if getattr(settings, "UMAP_XSENDFILE_HEADER", None):
|
|
|
|
|
response = HttpResponse()
|
2023-08-28 10:57:44 -05:00
|
|
|
|
response[settings.UMAP_XSENDFILE_HEADER] = f"/proxy/{quote(url)}"
|
Use X-Accel-Redirect for serving ajax-proxy request
uMap allows to use remote URL as data sources, but those URLs
are not always CORS open, so this is why there is this "ajax-proxy"
feature, where the URL is passed to the backend.
Additionally, there is a caching feature, which duration is configurable
through frontend settings. Valid values are: disabled, 5 min, 1 hour,
1 day.
Initially, I wanted this to be totally handled by Nginx, but I never
found a wayt to set the proxy_cache_valid value from a query string.
Since then, at least in OSM France servers, the ajax-proxy is still
handled by a Django view, which then opens the remote URL and transfert
the data. This is not optimal. And I suppose this is what is causing
hicups on the OSM France servers lately.
This PR provides a mix option, where python deals with validating the
URL and parsing the TTL parameter, and then it passes the hand to
nginx which will serve the remote content.
So, roughtly:
- the client calls /ajax-proxy/?url=xxx&ttl=300
- python will validate the URL (not internal calls…)
- if UMAP_SENDFILE_HEADER is set, then the python returns an empty
response with the path /proxy/http://url plus it will set the
cache ttl through the header X-Accel-Expires
- this /proxy/ location is then handled by nginx
2023-08-24 06:09:23 -05:00
|
|
|
|
if ttl:
|
|
|
|
|
response["X-Accel-Expires"] = ttl
|
|
|
|
|
return response
|
|
|
|
|
|
2023-08-28 10:57:44 -05:00
|
|
|
|
# You should not use this in production (use Nginx or so)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
headers = {"User-Agent": "uMapProxy +http://wiki.openstreetmap.org/wiki/UMap"}
|
2015-08-02 11:08:31 -05:00
|
|
|
|
request = Request(url, headers=headers)
|
|
|
|
|
opener = build_opener()
|
2014-04-19 04:48:54 -05:00
|
|
|
|
try:
|
2023-07-21 05:50:37 -05:00
|
|
|
|
proxied_request = opener.open(request, timeout=10)
|
2015-08-02 11:08:31 -05:00
|
|
|
|
except HTTPError as e:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return HttpResponse(e.msg, status=e.code, content_type="text/plain")
|
2023-06-02 15:36:02 -05:00
|
|
|
|
except URLError:
|
|
|
|
|
return HttpResponseBadRequest("URL error")
|
2023-06-02 15:45:13 -05:00
|
|
|
|
except InvalidURL:
|
|
|
|
|
return HttpResponseBadRequest("Invalid URL")
|
2023-08-17 10:43:25 -05:00
|
|
|
|
except TimeoutError:
|
|
|
|
|
return HttpResponseBadRequest("Timeout")
|
2014-04-19 04:48:54 -05:00
|
|
|
|
else:
|
2014-04-19 12:44:56 -05:00
|
|
|
|
status_code = proxied_request.code
|
2023-08-29 14:24:58 -05:00
|
|
|
|
content_type = proxied_request.headers.get("Content-Type")
|
|
|
|
|
if not content_type:
|
|
|
|
|
content_type, encoding = mimetypes.guess_type(url)
|
2014-04-19 12:44:56 -05:00
|
|
|
|
content = proxied_request.read()
|
2014-04-20 05:24:13 -05:00
|
|
|
|
# Quick hack to prevent Django from adding a Vary: Cookie header
|
|
|
|
|
self.request.session.accessed = False
|
2023-08-29 14:24:58 -05:00
|
|
|
|
response = HttpResponse(
|
|
|
|
|
content, status=status_code, content_type=content_type
|
|
|
|
|
)
|
Use X-Accel-Redirect for serving ajax-proxy request
uMap allows to use remote URL as data sources, but those URLs
are not always CORS open, so this is why there is this "ajax-proxy"
feature, where the URL is passed to the backend.
Additionally, there is a caching feature, which duration is configurable
through frontend settings. Valid values are: disabled, 5 min, 1 hour,
1 day.
Initially, I wanted this to be totally handled by Nginx, but I never
found a wayt to set the proxy_cache_valid value from a query string.
Since then, at least in OSM France servers, the ajax-proxy is still
handled by a Django view, which then opens the remote URL and transfert
the data. This is not optimal. And I suppose this is what is causing
hicups on the OSM France servers lately.
This PR provides a mix option, where python deals with validating the
URL and parsing the TTL parameter, and then it passes the hand to
nginx which will serve the remote content.
So, roughtly:
- the client calls /ajax-proxy/?url=xxx&ttl=300
- python will validate the URL (not internal calls…)
- if UMAP_SENDFILE_HEADER is set, then the python returns an empty
response with the path /proxy/http://url plus it will set the
cache ttl through the header X-Accel-Expires
- this /proxy/ location is then handled by nginx
2023-08-24 06:09:23 -05:00
|
|
|
|
if ttl:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
response["X-Accel-Expires"] = ttl
|
2018-08-04 13:50:03 -05:00
|
|
|
|
return response
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
|
|
|
|
|
2014-04-19 04:48:54 -05:00
|
|
|
|
ajax_proxy = AjaxProxy.as_view()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ############## #
|
|
|
|
|
# Utils #
|
|
|
|
|
# ############## #
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def simple_json_response(**kwargs):
|
2023-11-15 13:13:33 -06:00
|
|
|
|
return HttpResponse(json.dumps(kwargs), content_type="application/json")
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ############## #
|
|
|
|
|
# Map #
|
|
|
|
|
# ############## #
|
|
|
|
|
|
|
|
|
|
|
2018-07-07 09:44:40 -05:00
|
|
|
|
class FormLessEditMixin:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
http_method_names = [
|
|
|
|
|
"post",
|
|
|
|
|
]
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def form_invalid(self, form):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return simple_json_response(errors=form.errors, error=str(form.errors))
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2018-06-15 16:25:38 -05:00
|
|
|
|
def get_form(self, form_class=None):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
kwargs = self.get_form_kwargs()
|
2023-02-27 04:00:33 -06:00
|
|
|
|
kwargs["error_class"] = FlatErrorList
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return self.get_form_class()(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
2018-07-07 09:44:40 -05:00
|
|
|
|
class MapDetailMixin:
|
2018-05-19 04:12:19 -05:00
|
|
|
|
model = Map
|
2023-09-22 10:19:18 -05:00
|
|
|
|
pk_url_kwarg = "map_id"
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
2018-08-04 08:26:12 -05:00
|
|
|
|
context = super().get_context_data(**kwargs)
|
2023-09-20 11:26:04 -05:00
|
|
|
|
user = self.request.user
|
2018-05-19 04:12:19 -05:00
|
|
|
|
properties = {
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"urls": _urls_for_js(),
|
|
|
|
|
"tilelayers": TileLayer.get_list(),
|
2023-09-12 10:02:18 -05:00
|
|
|
|
"editMode": self.edit_mode,
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
|
|
|
|
|
"umap_id": self.get_umap_id(),
|
2023-05-22 16:47:04 -05:00
|
|
|
|
"starred": self.is_starred(),
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"licences": dict((l.name, l.json) for l in Licence.objects.all()),
|
|
|
|
|
"share_statuses": [
|
|
|
|
|
(i, str(label)) for i, label in Map.SHARE_STATUS if i != Map.BLOCKED
|
|
|
|
|
],
|
2023-06-06 13:05:35 -05:00
|
|
|
|
"umap_version": VERSION,
|
2023-09-20 12:16:52 -05:00
|
|
|
|
"featuresHaveOwner": settings.UMAP_DEFAULT_FEATURES_HAVE_OWNERS,
|
2018-05-19 04:12:19 -05:00
|
|
|
|
}
|
2023-09-20 11:26:04 -05:00
|
|
|
|
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
|
2023-09-08 10:14:37 -05:00
|
|
|
|
else:
|
2023-09-20 11:26:04 -05:00
|
|
|
|
map_statuses = AnonymousMapPermissionsForm.STATUS
|
|
|
|
|
datalayer_statuses = AnonymousDataLayerPermissionsForm.STATUS
|
2023-11-10 10:26:05 -06:00
|
|
|
|
properties["edit_statuses"] = [(i, str(label)) for i, label in map_statuses]
|
2023-09-20 11:26:04 -05:00
|
|
|
|
properties["datalayer_edit_statuses"] = [
|
|
|
|
|
(i, str(label)) for i, label in datalayer_statuses
|
|
|
|
|
]
|
2018-05-19 04:12:19 -05:00
|
|
|
|
if self.get_short_url():
|
2023-02-27 04:00:33 -06:00
|
|
|
|
properties["shortUrl"] = self.get_short_url()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
if settings.USE_I18N:
|
2023-07-03 12:44:04 -05:00
|
|
|
|
lang = settings.LANGUAGE_CODE
|
2018-05-19 04:12:19 -05:00
|
|
|
|
# Check attr in case the middleware is not active
|
|
|
|
|
if hasattr(self.request, "LANGUAGE_CODE"):
|
2023-07-03 12:44:04 -05:00
|
|
|
|
lang = self.request.LANGUAGE_CODE
|
|
|
|
|
properties["lang"] = lang
|
|
|
|
|
locale = to_locale(lang)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
properties["locale"] = locale
|
|
|
|
|
context["locale"] = locale
|
2018-09-23 02:56:30 -05:00
|
|
|
|
if not user.is_anonymous:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
properties["user"] = {
|
|
|
|
|
"id": user.pk,
|
2023-06-16 07:59:59 -05:00
|
|
|
|
"name": str(user),
|
2023-07-04 09:09:12 -05:00
|
|
|
|
"url": reverse("user_dashboard"),
|
2018-09-23 02:56:30 -05:00
|
|
|
|
}
|
2018-05-19 04:12:19 -05:00
|
|
|
|
map_settings = self.get_geojson()
|
|
|
|
|
if "properties" not in map_settings:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
map_settings["properties"] = {}
|
|
|
|
|
map_settings["properties"].update(properties)
|
|
|
|
|
map_settings["properties"]["datalayers"] = self.get_datalayers()
|
|
|
|
|
context["map_settings"] = json.dumps(map_settings, indent=settings.DEBUG)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
def get_datalayers(self):
|
|
|
|
|
return []
|
|
|
|
|
|
2023-09-12 10:02:18 -05:00
|
|
|
|
@property
|
|
|
|
|
def edit_mode(self):
|
|
|
|
|
return "advanced"
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2018-06-02 08:43:22 -05:00
|
|
|
|
def get_umap_id(self):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return None
|
|
|
|
|
|
2019-04-09 02:42:09 -05:00
|
|
|
|
def is_starred(self):
|
|
|
|
|
return False
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def get_geojson(self):
|
|
|
|
|
return {
|
|
|
|
|
"geometry": {
|
|
|
|
|
"coordinates": [DEFAULT_LONGITUDE, DEFAULT_LATITUDE],
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"type": "Point",
|
2018-05-19 04:12:19 -05:00
|
|
|
|
},
|
|
|
|
|
"properties": {
|
2023-02-27 04:00:33 -06:00
|
|
|
|
"zoom": getattr(settings, "LEAFLET_ZOOM", 6),
|
2018-05-19 04:12:19 -05:00
|
|
|
|
"datalayers": [],
|
2023-02-27 04:00:33 -06:00
|
|
|
|
},
|
2018-05-19 04:12:19 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def get_short_url(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2018-09-23 02:56:30 -05:00
|
|
|
|
class PermissionsMixin:
|
|
|
|
|
def get_permissions(self):
|
|
|
|
|
permissions = {}
|
2023-09-18 13:05:17 -05:00
|
|
|
|
permissions["edit_status"] = self.object.edit_status
|
2023-02-27 04:00:33 -06:00
|
|
|
|
permissions["share_status"] = self.object.share_status
|
2018-09-23 02:56:30 -05:00
|
|
|
|
if self.object.owner:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
permissions["owner"] = {
|
|
|
|
|
"id": self.object.owner.pk,
|
2023-06-16 07:59:59 -05:00
|
|
|
|
"name": str(self.object.owner),
|
|
|
|
|
"url": self.object.owner.get_url(),
|
2018-09-23 02:56:30 -05:00
|
|
|
|
}
|
2023-02-27 04:00:33 -06:00
|
|
|
|
permissions["editors"] = [
|
2023-06-16 07:59:59 -05:00
|
|
|
|
{"id": editor.pk, "name": str(editor)}
|
2023-02-27 04:00:33 -06:00
|
|
|
|
for editor in self.object.editors.all()
|
|
|
|
|
]
|
|
|
|
|
if not self.object.owner and self.object.is_anonymous_owner(self.request):
|
2023-05-31 09:30:07 -05:00
|
|
|
|
permissions["anonymous_edit_url"] = self.object.get_anonymous_edit_url()
|
2018-09-23 02:56:30 -05:00
|
|
|
|
return permissions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
|
self.object = self.get_object()
|
|
|
|
|
canonical = self.get_canonical_url()
|
|
|
|
|
if not request.path == canonical:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
if request.META.get("QUERY_STRING"):
|
|
|
|
|
canonical = "?".join([canonical, request.META["QUERY_STRING"]])
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return HttpResponsePermanentRedirect(canonical)
|
|
|
|
|
return super(MapView, self).get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def get_canonical_url(self):
|
|
|
|
|
return self.object.get_absolute_url()
|
|
|
|
|
|
|
|
|
|
def get_datalayers(self):
|
2023-09-07 03:31:25 -05:00
|
|
|
|
return [
|
|
|
|
|
l.metadata(self.request.user, self.request)
|
|
|
|
|
for l in self.object.datalayer_set.all()
|
|
|
|
|
]
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-09-12 10:02:18 -05:00
|
|
|
|
@property
|
|
|
|
|
def edit_mode(self):
|
2023-09-18 13:05:17 -05:00
|
|
|
|
edit_mode = "disabled"
|
2023-09-12 10:02:18 -05:00
|
|
|
|
if self.object.can_edit(self.request.user, self.request):
|
|
|
|
|
edit_mode = "advanced"
|
|
|
|
|
elif any(
|
2023-09-07 03:31:25 -05:00
|
|
|
|
d.can_edit(self.request.user, self.request)
|
|
|
|
|
for d in self.object.datalayer_set.all()
|
2023-09-12 10:02:18 -05:00
|
|
|
|
):
|
|
|
|
|
edit_mode = "simple"
|
|
|
|
|
return edit_mode
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2018-06-02 08:43:22 -05:00
|
|
|
|
def get_umap_id(self):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return self.object.pk
|
|
|
|
|
|
|
|
|
|
def get_short_url(self):
|
2023-09-26 03:34:50 -05:00
|
|
|
|
short_url = None
|
|
|
|
|
if getattr(settings, "SHORT_SITE_URL", None):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
short_path = reverse_lazy("map_short_url", kwargs={"pk": self.object.pk})
|
2023-09-26 03:34:50 -05:00
|
|
|
|
short_url = "%s%s" % (settings.SHORT_SITE_URL, short_path)
|
|
|
|
|
return short_url
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def get_geojson(self):
|
|
|
|
|
map_settings = self.object.settings
|
|
|
|
|
if "properties" not in map_settings:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
map_settings["properties"] = {}
|
|
|
|
|
map_settings["properties"]["name"] = self.object.name
|
|
|
|
|
map_settings["properties"]["permissions"] = self.get_permissions()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return map_settings
|
|
|
|
|
|
2019-04-09 02:42:09 -05:00
|
|
|
|
def is_starred(self):
|
|
|
|
|
user = self.request.user
|
|
|
|
|
if not user.is_authenticated:
|
|
|
|
|
return False
|
|
|
|
|
return Star.objects.filter(by=user, map=self.object).exists()
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-11-08 14:20:59 -06:00
|
|
|
|
class MapDownload(DetailView):
|
|
|
|
|
model = Map
|
|
|
|
|
pk_url_kwarg = "map_id"
|
|
|
|
|
|
|
|
|
|
def get_canonical_url(self):
|
|
|
|
|
return reverse("map_download", args=(self.object.pk,))
|
|
|
|
|
|
|
|
|
|
def render_to_response(self, context, *args, **kwargs):
|
|
|
|
|
geojson = self.object.settings
|
|
|
|
|
geojson["type"] = "umap"
|
|
|
|
|
geojson["uri"] = self.request.build_absolute_uri(self.object.get_absolute_url())
|
|
|
|
|
datalayers = []
|
|
|
|
|
for datalayer in self.object.datalayer_set.all():
|
|
|
|
|
with open(datalayer.geojson.path, "rb") as f:
|
|
|
|
|
layer = json.loads(f.read())
|
|
|
|
|
if datalayer.settings:
|
|
|
|
|
layer["_umap_options"] = datalayer.settings
|
|
|
|
|
datalayers.append(layer)
|
|
|
|
|
geojson["layers"] = datalayers
|
2023-11-14 12:26:52 -06:00
|
|
|
|
response = simple_json_response(**geojson)
|
|
|
|
|
response[
|
|
|
|
|
"Content-Disposition"
|
|
|
|
|
] = f'attachment; filename="umap_backup_{self.object.slug}.umap"'
|
|
|
|
|
return response
|
2023-11-08 14:20:59 -06:00
|
|
|
|
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
class MapViewGeoJSON(MapView):
|
|
|
|
|
def get_canonical_url(self):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return reverse("map_geojson", args=(self.object.pk,))
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def render_to_response(self, context, *args, **kwargs):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return HttpResponse(context["map_settings"], content_type="application/json")
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MapNew(MapDetailMixin, TemplateView):
|
|
|
|
|
template_name = "umap/map_detail.html"
|
|
|
|
|
|
|
|
|
|
|
2018-09-23 02:56:30 -05:00
|
|
|
|
class MapCreate(FormLessEditMixin, PermissionsMixin, CreateView):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
model = Map
|
|
|
|
|
form_class = MapSettingsForm
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
if self.request.user.is_authenticated:
|
|
|
|
|
form.instance.owner = self.request.user
|
|
|
|
|
self.object = form.save()
|
2018-09-23 02:56:30 -05:00
|
|
|
|
permissions = self.get_permissions()
|
|
|
|
|
# User does not have the cookie yet.
|
2023-06-04 01:05:48 -05:00
|
|
|
|
if not self.object.owner:
|
|
|
|
|
anonymous_url = self.object.get_anonymous_edit_url()
|
|
|
|
|
permissions["anonymous_edit_url"] = anonymous_url
|
2018-05-19 04:12:19 -05:00
|
|
|
|
response = simple_json_response(
|
|
|
|
|
id=self.object.pk,
|
|
|
|
|
url=self.object.get_absolute_url(),
|
2018-09-23 02:56:30 -05:00
|
|
|
|
permissions=permissions,
|
2018-05-19 04:12:19 -05:00
|
|
|
|
)
|
|
|
|
|
if not self.request.user.is_authenticated:
|
|
|
|
|
key, value = self.object.signed_cookie_elements
|
|
|
|
|
response.set_signed_cookie(
|
2023-02-27 04:00:33 -06:00
|
|
|
|
key=key, value=value, max_age=ANONYMOUS_COOKIE_MAX_AGE
|
2018-05-19 04:12:19 -05:00
|
|
|
|
)
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
2018-09-23 02:56:30 -05:00
|
|
|
|
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
model = Map
|
|
|
|
|
form_class = MapSettingsForm
|
2023-02-27 04:00:33 -06:00
|
|
|
|
pk_url_kwarg = "map_id"
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
self.object.settings = form.cleaned_data["settings"]
|
|
|
|
|
self.object.save()
|
|
|
|
|
return simple_json_response(
|
|
|
|
|
id=self.object.pk,
|
|
|
|
|
url=self.object.get_absolute_url(),
|
2018-09-23 02:56:30 -05:00
|
|
|
|
permissions=self.get_permissions(),
|
|
|
|
|
info=_("Map has been updated!"),
|
2018-05-19 04:12:19 -05:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2018-06-15 16:25:38 -05:00
|
|
|
|
class UpdateMapPermissions(FormLessEditMixin, UpdateView):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
model = Map
|
2023-02-27 04:00:33 -06:00
|
|
|
|
pk_url_kwarg = "map_id"
|
2023-09-18 13:05:17 -05:00
|
|
|
|
|
|
|
|
|
def get_form_class(self):
|
|
|
|
|
if self.object.owner:
|
|
|
|
|
return UpdateMapPermissionsForm
|
|
|
|
|
else:
|
|
|
|
|
return AnonymousMapPermissionsForm
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def get_form(self, form_class=None):
|
2018-06-15 16:25:38 -05:00
|
|
|
|
form = super().get_form(form_class)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
user = self.request.user
|
|
|
|
|
if self.object.owner and not user == self.object.owner:
|
2023-09-18 13:05:17 -05:00
|
|
|
|
del form.fields["edit_status"]
|
2023-02-27 04:00:33 -06:00
|
|
|
|
del form.fields["share_status"]
|
|
|
|
|
del form.fields["owner"]
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return form
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
self.object = form.save()
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return simple_json_response(info=_("Map editors updated with success!"))
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
2018-07-07 09:44:40 -05:00
|
|
|
|
class AttachAnonymousMap(View):
|
|
|
|
|
def post(self, *args, **kwargs):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
self.object = kwargs["map_inst"]
|
|
|
|
|
if (
|
|
|
|
|
self.object.owner
|
|
|
|
|
or not self.object.is_anonymous_owner(self.request)
|
|
|
|
|
or not self.object.can_edit(self.request.user, self.request)
|
|
|
|
|
or not self.request.user.is_authenticated
|
|
|
|
|
):
|
2018-09-08 09:49:25 -05:00
|
|
|
|
return HttpResponseForbidden()
|
2018-07-07 09:44:40 -05:00
|
|
|
|
self.object.owner = self.request.user
|
|
|
|
|
self.object.save()
|
|
|
|
|
return simple_json_response()
|
|
|
|
|
|
|
|
|
|
|
2023-05-31 10:05:57 -05:00
|
|
|
|
class SendEditLink(FormLessEditMixin, FormView):
|
|
|
|
|
form_class = SendLinkForm
|
2021-12-29 10:34:11 -06:00
|
|
|
|
|
2023-05-31 10:05:57 -05:00
|
|
|
|
def post(self, form, **kwargs):
|
|
|
|
|
self.object = kwargs["map_inst"]
|
|
|
|
|
if (
|
|
|
|
|
self.object.owner
|
|
|
|
|
or not self.object.is_anonymous_owner(self.request)
|
|
|
|
|
or not self.object.can_edit(self.request.user, self.request)
|
|
|
|
|
):
|
2021-12-29 10:34:11 -06:00
|
|
|
|
return HttpResponseForbidden()
|
2023-05-31 10:05:57 -05:00
|
|
|
|
form = self.get_form()
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
email = form.cleaned_data["email"]
|
|
|
|
|
else:
|
|
|
|
|
return HttpResponseBadRequest("Invalid")
|
|
|
|
|
link = self.object.get_anonymous_edit_url()
|
2021-12-29 10:34:11 -06:00
|
|
|
|
|
|
|
|
|
send_mail(
|
2023-06-06 13:05:35 -05:00
|
|
|
|
_(
|
|
|
|
|
"The uMap edit link for your map: %(map_name)s"
|
|
|
|
|
% {"map_name": self.object.name}
|
|
|
|
|
),
|
2023-05-31 10:05:57 -05:00
|
|
|
|
_("Here is your secret edit link: %(link)s" % {"link": link}),
|
2021-12-29 10:34:11 -06:00
|
|
|
|
settings.FROM_EMAIL,
|
|
|
|
|
[email],
|
|
|
|
|
fail_silently=False,
|
|
|
|
|
)
|
2023-05-31 10:05:57 -05:00
|
|
|
|
return simple_json_response(
|
|
|
|
|
info=_("Email sent to %(email)s" % {"email": email})
|
|
|
|
|
)
|
2021-12-29 10:34:11 -06:00
|
|
|
|
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
class MapDelete(DeleteView):
|
|
|
|
|
model = Map
|
|
|
|
|
pk_url_kwarg = "map_id"
|
|
|
|
|
|
2023-02-22 08:19:38 -06:00
|
|
|
|
def form_valid(self, form):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
self.object = self.get_object()
|
|
|
|
|
if self.object.owner and self.request.user != self.object.owner:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return HttpResponseForbidden(_("Only its owner can delete the map."))
|
|
|
|
|
if not self.object.owner and not self.object.is_anonymous_owner(self.request):
|
2018-09-08 09:49:25 -05:00
|
|
|
|
return HttpResponseForbidden()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
self.object.delete()
|
|
|
|
|
return simple_json_response(redirect="/")
|
|
|
|
|
|
|
|
|
|
|
2018-09-23 02:56:30 -05:00
|
|
|
|
class MapClone(PermissionsMixin, View):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def post(self, *args, **kwargs):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
if (
|
|
|
|
|
not getattr(settings, "UMAP_ALLOW_ANONYMOUS", False)
|
|
|
|
|
and not self.request.user.is_authenticated
|
|
|
|
|
):
|
2018-09-08 09:49:25 -05:00
|
|
|
|
return HttpResponseForbidden()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
owner = self.request.user if self.request.user.is_authenticated else None
|
2023-02-27 04:00:33 -06:00
|
|
|
|
self.object = kwargs["map_inst"].clone(owner=owner)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
response = simple_json_response(redirect=self.object.get_absolute_url())
|
|
|
|
|
if not self.request.user.is_authenticated:
|
|
|
|
|
key, value = self.object.signed_cookie_elements
|
|
|
|
|
response.set_signed_cookie(
|
2023-02-27 04:00:33 -06:00
|
|
|
|
key=key, value=value, max_age=ANONYMOUS_COOKIE_MAX_AGE
|
2018-05-19 04:12:19 -05:00
|
|
|
|
)
|
|
|
|
|
msg = _(
|
|
|
|
|
"Your map has been cloned! If you want to edit this map from "
|
|
|
|
|
"another computer, please use this link: %(anonymous_url)s"
|
2023-05-31 09:30:07 -05:00
|
|
|
|
% {"anonymous_url": self.object.get_anonymous_edit_url()}
|
2018-05-19 04:12:19 -05:00
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
msg = _("Congratulations, your map has been cloned!")
|
|
|
|
|
messages.info(self.request, msg)
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
2023-05-05 15:13:10 -05:00
|
|
|
|
class ToggleMapStarStatus(View):
|
2019-04-09 02:42:09 -05:00
|
|
|
|
def post(self, *args, **kwargs):
|
2023-05-22 16:47:04 -05:00
|
|
|
|
map_inst = get_object_or_404(Map, pk=kwargs["map_id"])
|
2019-04-09 02:42:09 -05:00
|
|
|
|
qs = Star.objects.filter(map=map_inst, by=self.request.user)
|
|
|
|
|
if qs.exists():
|
|
|
|
|
qs.delete()
|
|
|
|
|
status = False
|
|
|
|
|
else:
|
2023-05-05 15:10:25 -05:00
|
|
|
|
Star.objects.create(map=map_inst, by=self.request.user)
|
2019-04-09 02:42:09 -05:00
|
|
|
|
status = True
|
|
|
|
|
return simple_json_response(starred=status)
|
|
|
|
|
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
class MapShortUrl(RedirectView):
|
|
|
|
|
query_string = True
|
|
|
|
|
permanent = True
|
|
|
|
|
|
|
|
|
|
def get_redirect_url(self, **kwargs):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
map_inst = get_object_or_404(Map, pk=kwargs["pk"])
|
2018-05-19 04:12:19 -05:00
|
|
|
|
url = map_inst.get_absolute_url()
|
|
|
|
|
if self.query_string:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
args = self.request.META.get("QUERY_STRING", "")
|
2018-05-19 04:12:19 -05:00
|
|
|
|
if args:
|
|
|
|
|
url = "%s?%s" % (url, args)
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MapAnonymousEditUrl(RedirectView):
|
|
|
|
|
permanent = False
|
|
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
|
signer = Signer()
|
|
|
|
|
try:
|
2023-02-27 04:00:33 -06:00
|
|
|
|
pk = signer.unsign(self.kwargs["signature"])
|
2018-05-19 04:12:19 -05:00
|
|
|
|
except BadSignature:
|
2023-05-11 04:33:30 -05:00
|
|
|
|
signer = Signer(algorithm="sha1")
|
2023-03-27 09:26:32 -05:00
|
|
|
|
try:
|
|
|
|
|
pk = signer.unsign(self.kwargs["signature"])
|
|
|
|
|
except BadSignature:
|
|
|
|
|
return HttpResponseForbidden()
|
|
|
|
|
|
|
|
|
|
map_inst = get_object_or_404(Map, pk=pk)
|
|
|
|
|
url = map_inst.get_absolute_url()
|
|
|
|
|
response = HttpResponseRedirect(url)
|
|
|
|
|
if not map_inst.owner:
|
|
|
|
|
key, value = map_inst.signed_cookie_elements
|
|
|
|
|
response.set_signed_cookie(
|
|
|
|
|
key=key, value=value, max_age=ANONYMOUS_COOKIE_MAX_AGE
|
|
|
|
|
)
|
|
|
|
|
return response
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ############## #
|
|
|
|
|
# DataLayer #
|
|
|
|
|
# ############## #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GZipMixin(object):
|
2023-02-27 04:38:59 -06:00
|
|
|
|
EXT = ".gz"
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-02-27 05:04:09 -06:00
|
|
|
|
@property
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def path(self):
|
2023-02-27 05:04:09 -06:00
|
|
|
|
return self.object.geojson.path
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-07-19 07:17:40 -05:00
|
|
|
|
@property
|
|
|
|
|
def gzip_path(self):
|
|
|
|
|
return Path(f"{self.path}{self.EXT}")
|
|
|
|
|
|
2020-03-25 02:56:43 -05:00
|
|
|
|
def compute_last_modified(self, path):
|
|
|
|
|
stat = os.stat(path)
|
|
|
|
|
return http_date(stat.st_mtime)
|
|
|
|
|
|
2023-02-27 06:45:15 -06:00
|
|
|
|
@property
|
|
|
|
|
def last_modified(self):
|
2023-07-19 07:17:40 -05:00
|
|
|
|
# Prior to 1.3.0 we did not set gzip mtime as geojson mtime,
|
|
|
|
|
# but we switched from If-Match header to IF-Unmodified-Since
|
|
|
|
|
# and when users accepts gzip their last modified value is the gzip
|
|
|
|
|
# (when umap is served by nginx and X-Accel-Redirect)
|
|
|
|
|
# one, so we need to compare with that value in that case.
|
|
|
|
|
# cf https://github.com/umap-project/umap/issues/1212
|
2023-07-20 02:02:40 -05:00
|
|
|
|
path = (
|
|
|
|
|
self.gzip_path
|
|
|
|
|
if self.accepts_gzip and self.gzip_path.exists()
|
|
|
|
|
else self.path
|
|
|
|
|
)
|
2020-03-25 02:56:43 -05:00
|
|
|
|
return self.compute_last_modified(path)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-07-19 07:17:40 -05:00
|
|
|
|
@property
|
|
|
|
|
def accepts_gzip(self):
|
|
|
|
|
return settings.UMAP_GZIP and re_accepts_gzip.search(
|
|
|
|
|
self.request.META.get("HTTP_ACCEPT_ENCODING", "")
|
|
|
|
|
)
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
class DataLayerView(GZipMixin, BaseDetailView):
|
|
|
|
|
model = DataLayer
|
|
|
|
|
|
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
|
|
|
response = None
|
2023-02-27 05:04:09 -06:00
|
|
|
|
path = self.path
|
|
|
|
|
# Generate gzip if needed
|
2023-07-19 07:17:40 -05:00
|
|
|
|
if self.accepts_gzip:
|
|
|
|
|
if not self.gzip_path.exists():
|
|
|
|
|
gzip_file(path, self.gzip_path)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2023-02-27 04:38:59 -06:00
|
|
|
|
if getattr(settings, "UMAP_XSENDFILE_HEADER", None):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
response = HttpResponse()
|
2023-02-27 04:00:33 -06:00
|
|
|
|
path = path.replace(settings.MEDIA_ROOT, "/internal")
|
2018-05-19 10:16:34 -05:00
|
|
|
|
response[settings.UMAP_XSENDFILE_HEADER] = path
|
2018-05-19 04:12:19 -05:00
|
|
|
|
else:
|
2023-02-27 05:04:09 -06:00
|
|
|
|
# Do not use in production
|
2023-02-27 06:47:12 -06:00
|
|
|
|
# (no gzip/cache-control/If-Modified-Since/If-None-Match)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
statobj = os.stat(path)
|
2023-02-27 04:00:33 -06:00
|
|
|
|
with open(path, "rb") as f:
|
|
|
|
|
# Should not be used in production!
|
2023-02-27 05:04:09 -06:00
|
|
|
|
response = HttpResponse(f.read(), content_type="application/geo+json")
|
2023-02-27 06:45:15 -06:00
|
|
|
|
response["Last-Modified"] = self.last_modified
|
2023-02-27 04:38:59 -06:00
|
|
|
|
response["Content-Length"] = statobj.st_size
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataLayerVersion(DataLayerView):
|
2023-02-27 05:04:09 -06:00
|
|
|
|
@property
|
|
|
|
|
def path(self):
|
2023-02-27 04:00:33 -06:00
|
|
|
|
return "{root}/{path}".format(
|
2018-05-19 04:12:19 -05:00
|
|
|
|
root=settings.MEDIA_ROOT,
|
2023-02-27 04:00:33 -06:00
|
|
|
|
path=self.object.get_version_path(self.kwargs["name"]),
|
|
|
|
|
)
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView):
|
|
|
|
|
model = DataLayer
|
|
|
|
|
form_class = DataLayerForm
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
2023-02-27 04:38:59 -06:00
|
|
|
|
form.instance.map = self.kwargs["map_inst"]
|
2018-05-19 04:12:19 -05:00
|
|
|
|
self.object = form.save()
|
2023-02-25 14:50:39 -06:00
|
|
|
|
# Simple response with only metadatas (including new id)
|
2023-09-07 03:31:25 -05:00
|
|
|
|
response = simple_json_response(
|
|
|
|
|
**self.object.metadata(self.request.user, self.request)
|
|
|
|
|
)
|
2023-02-27 06:45:15 -06:00
|
|
|
|
response["Last-Modified"] = self.last_modified
|
2018-05-19 04:12:19 -05:00
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|
|
|
|
model = DataLayer
|
|
|
|
|
form_class = DataLayerForm
|
|
|
|
|
|
2020-03-25 02:56:43 -05:00
|
|
|
|
def has_been_modified_since(self, if_unmodified_since):
|
|
|
|
|
return if_unmodified_since and self.last_modified != if_unmodified_since
|
|
|
|
|
|
|
|
|
|
def merge(self, if_unmodified_since):
|
|
|
|
|
"""
|
|
|
|
|
Attempt to apply the incoming changes to the document the client was using, and
|
|
|
|
|
then merge it with the last document we have on storage.
|
|
|
|
|
|
|
|
|
|
Returns either None (if the merge failed) or the merged python GeoJSON object.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Use If-Modified-Since to find the correct version in our storage.
|
|
|
|
|
for name in self.object.get_versions():
|
|
|
|
|
path = os.path.join(settings.MEDIA_ROOT, self.object.get_version_path(name))
|
|
|
|
|
if if_unmodified_since == self.compute_last_modified(path):
|
|
|
|
|
with open(path) as f:
|
|
|
|
|
reference = json.loads(f.read())
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
# If the document is not found, we can't merge.
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# New data received in the request.
|
|
|
|
|
entrant = json.loads(self.request.FILES["geojson"].read())
|
|
|
|
|
|
|
|
|
|
# Latest known version of the data.
|
|
|
|
|
with open(self.path) as f:
|
|
|
|
|
latest = json.loads(f.read())
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
2020-03-25 02:56:43 -05:00
|
|
|
|
try:
|
|
|
|
|
merged_features = merge_features(
|
|
|
|
|
reference["features"], latest["features"], entrant["features"]
|
|
|
|
|
)
|
|
|
|
|
latest["features"] = merged_features
|
|
|
|
|
return latest
|
|
|
|
|
except ConflictError:
|
|
|
|
|
return None
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
|
self.object = self.get_object()
|
2023-09-08 08:22:08 -05:00
|
|
|
|
if self.object.map.pk != int(self.kwargs["map_id"]):
|
2018-09-08 09:49:25 -05:00
|
|
|
|
return HttpResponseForbidden()
|
2020-03-25 02:56:43 -05:00
|
|
|
|
|
2023-09-08 02:39:28 -05:00
|
|
|
|
if not self.object.can_edit(user=self.request.user, request=self.request):
|
|
|
|
|
return HttpResponseForbidden()
|
2020-03-25 02:56:43 -05:00
|
|
|
|
|
|
|
|
|
ius_header = self.request.META.get("HTTP_IF_UNMODIFIED_SINCE")
|
|
|
|
|
|
|
|
|
|
if self.has_been_modified_since(ius_header):
|
|
|
|
|
merged = self.merge(ius_header)
|
|
|
|
|
if not merged:
|
|
|
|
|
return HttpResponse(status=412)
|
|
|
|
|
|
|
|
|
|
# Replace the uploaded file by the merged version.
|
|
|
|
|
self.request.FILES["geojson"].file = BytesIO(
|
|
|
|
|
json.dumps(merged).encode("utf-8")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Mark the data to be reloaded by form_valid
|
|
|
|
|
self.request.session["needs_reload"] = True
|
|
|
|
|
return super().post(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
|
self.object = form.save()
|
|
|
|
|
data = {**self.object.metadata(self.request.user, self.request)}
|
|
|
|
|
if self.request.session.get("needs_reload"):
|
|
|
|
|
data["geojson"] = json.loads(self.object.geojson.read().decode())
|
|
|
|
|
self.request.session["needs_reload"] = False
|
|
|
|
|
response = simple_json_response(**data)
|
|
|
|
|
|
|
|
|
|
response["Last-Modified"] = self.last_modified
|
|
|
|
|
return response
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataLayerDelete(DeleteView):
|
|
|
|
|
model = DataLayer
|
|
|
|
|
|
2023-02-22 08:19:38 -06:00
|
|
|
|
def form_valid(self, form):
|
2018-05-19 04:12:19 -05:00
|
|
|
|
self.object = self.get_object()
|
2023-02-27 04:00:33 -06:00
|
|
|
|
if self.object.map != self.kwargs["map_inst"]:
|
2018-09-08 09:49:25 -05:00
|
|
|
|
return HttpResponseForbidden()
|
2018-05-19 04:12:19 -05:00
|
|
|
|
self.object.delete()
|
|
|
|
|
return simple_json_response(info=_("Layer successfully deleted."))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataLayerVersions(BaseDetailView):
|
|
|
|
|
model = DataLayer
|
|
|
|
|
|
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
|
|
|
return simple_json_response(versions=self.object.versions)
|
|
|
|
|
|
|
|
|
|
|
2023-09-07 03:31:25 -05:00
|
|
|
|
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!"))
|
|
|
|
|
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
# ############## #
|
|
|
|
|
# Picto #
|
|
|
|
|
# ############## #
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
class PictogramJSONList(ListView):
|
|
|
|
|
model = Pictogram
|
|
|
|
|
|
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
|
|
|
content = [p.json for p in Pictogram.objects.all()]
|
|
|
|
|
return simple_json_response(pictogram_list=content)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ############## #
|
|
|
|
|
# Generic #
|
|
|
|
|
# ############## #
|
|
|
|
|
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2023-05-22 16:47:04 -05:00
|
|
|
|
def stats(request):
|
2023-11-23 17:34:24 -06:00
|
|
|
|
last_week = make_aware(datetime.now()) - timedelta(days=7)
|
2023-05-22 16:47:04 -05:00
|
|
|
|
return simple_json_response(
|
|
|
|
|
**{
|
2023-06-20 08:14:28 -05:00
|
|
|
|
"version": VERSION,
|
2023-05-22 16:47:04 -05:00
|
|
|
|
"maps_count": Map.objects.count(),
|
|
|
|
|
"maps_active_last_week_count": Map.objects.filter(
|
|
|
|
|
modified_at__gt=last_week
|
|
|
|
|
).count(),
|
|
|
|
|
"users_count": User.objects.count(),
|
|
|
|
|
"users_active_last_week_count": User.objects.filter(
|
|
|
|
|
last_login__gt=last_week
|
|
|
|
|
).count(),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-10 10:26:05 -06:00
|
|
|
|
@require_GET
|
2023-11-14 21:50:25 -06:00
|
|
|
|
@cache_control(max_age=60 * 60 * 24, immutable=True, public=True) # One day.
|
|
|
|
|
def webmanifest(request):
|
|
|
|
|
return simple_json_response(
|
|
|
|
|
**{
|
|
|
|
|
"icons": [
|
|
|
|
|
{
|
|
|
|
|
"src": staticfiles_storage.url("umap/favicons/icon-192.png"),
|
|
|
|
|
"type": "image/png",
|
|
|
|
|
"sizes": "192x192",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"src": staticfiles_storage.url("umap/favicons/icon-512.png"),
|
|
|
|
|
"type": "image/png",
|
|
|
|
|
"sizes": "512x512",
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
}
|
2023-11-10 10:26:05 -06:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
def logout(request):
|
|
|
|
|
do_logout(request)
|
2023-07-27 08:02:51 -05:00
|
|
|
|
if is_ajax(request):
|
|
|
|
|
return simple_json_response(redirect="/")
|
|
|
|
|
return HttpResponseRedirect("/")
|
2018-05-19 04:12:19 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginPopupEnd(TemplateView):
|
|
|
|
|
"""
|
|
|
|
|
End of a loggin process in popup.
|
|
|
|
|
Basically close the popup.
|
|
|
|
|
"""
|
2023-02-27 04:00:33 -06:00
|
|
|
|
|
2018-05-19 04:12:19 -05:00
|
|
|
|
template_name = "umap/login_popup_end.html"
|