Compare commits

..

No commits in common. "5ae83a571be75bd16550e921a5de7ee46610e860" and "5ab11428a5815a0edfd61575e0e2bbdb334a4967" have entirely different histories.

40 changed files with 54 additions and 200 deletions

View file

@ -28,14 +28,6 @@ Can be set through env var too: `ALLOWED_HOSTS=umap.mydomain.org,u.mydomain.org`
Set it to `True` for easier debugging in case of error.
#### DEPRECATED_AUTHENTICATION_PROVIDERS
List of auth providers to deprecate. Defining this will display a message to
all users using this provider, to encourage them to configure another provider to
their account.
DEPRECATED_AUTHENTICATION_PROVIDERS = ["social_core.backends.twitter_oauth2.TwitterOAuth2"]
#### EMAIL_BACKEND
Must be configured if you want uMap to send emails to anonymous users.
@ -106,12 +98,7 @@ Eg.: `SHORT_SITE_URL=https://u.umap.org`
#### SITE_NAME
The name of the site, to be used in header.
#### SITE_DESCRIPTION
The description of the site, to be used in HTML title.
The name of the site, to be used in header and HTML title.
#### SITE_URL

View file

@ -7,7 +7,6 @@ def settings(request):
return {
"UMAP_HELP_URL": djsettings.UMAP_HELP_URL,
"SITE_NAME": djsettings.SITE_NAME,
"SITE_DESCRIPTION": djsettings.SITE_DESCRIPTION,
"SITE_URL": djsettings.SITE_URL,
"ENABLE_ACCOUNT_LOGIN": djsettings.ENABLE_ACCOUNT_LOGIN,
"UMAP_READONLY": djsettings.UMAP_READONLY,

View file

@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from django.core.management.base import BaseCommand
from umap.models import DataLayer, Map
from umap.models import Map
class Command(BaseCommand):
@ -33,14 +33,3 @@ class Command(BaseCommand):
if not options["dry_run"]:
map.delete()
print(f"Deleted map {map_name} ({map_id}), trashed at {trashed_at}")
print(f"Deleting layers in trash since {since}")
layers = DataLayer.objects.filter(
share_status=DataLayer.DELETED, modified_at__lt=since
)
for layer in layers:
layer_id = layer.uuid
layer_name = layer.name
trashed_at = layer.modified_at.date()
if not options["dry_run"]:
layer.delete()
print(f"Deleted layer {layer_name} ({layer_id}), trashed at {trashed_at}")

View file

@ -1,26 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-29 18:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("umap", "0025_alter_datalayer_geojson"),
]
operations = [
migrations.AddField(
model_name="datalayer",
name="modified_at",
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name="datalayer",
name="share_status",
field=models.SmallIntegerField(
choices=[(0, "Inherit"), (99, "Deleted")],
default=0,
verbose_name="share status",
),
),
]

View file

@ -247,13 +247,9 @@ class Map(NamedModel):
except KeyError:
return ""
@property
def datalayers(self):
return self.datalayer_set.filter(share_status=DataLayer.INHERIT).all()
@property
def preview_settings(self):
layers = self.datalayers
layers = self.datalayer_set.all()
datalayer_data = [c.metadata() for c in layers]
map_settings = self.settings
if "properties" not in map_settings:
@ -282,7 +278,6 @@ class Map(NamedModel):
def delete(self, **kwargs):
# Explicitely call datalayers.delete, so we can deal with removing files
# (the cascade delete would not call the model delete method)
# Use datalayer_set so to get also the deleted ones.
for datalayer in self.datalayer_set.all():
datalayer.delete()
return super().delete(**kwargs)
@ -292,7 +287,7 @@ class Map(NamedModel):
umapjson["type"] = "umap"
umapjson["uri"] = request.build_absolute_uri(self.get_absolute_url())
datalayers = []
for datalayer in self.datalayers:
for datalayer in self.datalayer_set.all():
with datalayer.geojson.open("rb") as f:
layer = json.loads(f.read())
if datalayer.settings:
@ -411,7 +406,7 @@ class Map(NamedModel):
new.save()
for editor in self.editors.all():
new.editors.add(editor)
for datalayer in self.datalayers:
for datalayer in self.datalayer_set.all():
datalayer.clone(map_inst=new)
return new
@ -463,11 +458,6 @@ class DataLayer(NamedModel):
ANONYMOUS = 1
COLLABORATORS = 2
OWNER = 3
DELETED = 99
SHARE_STATUS = (
(INHERIT, _("Inherit")),
(DELETED, _("Deleted")),
)
EDIT_STATUS = (
(INHERIT, _("Inherit")),
(ANONYMOUS, _("Everyone")),
@ -500,12 +490,6 @@ class DataLayer(NamedModel):
default=INHERIT,
verbose_name=_("edit status"),
)
share_status = models.SmallIntegerField(
choices=SHARE_STATUS,
default=INHERIT,
verbose_name=_("share status"),
)
modified_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ("rank",)
@ -584,10 +568,6 @@ class DataLayer(NamedModel):
can = True
return can
def move_to_trash(self):
self.share_status = DataLayer.DELETED
self.save()
class Star(models.Model):
at = models.DateTimeField(auto_now=True)

View file

@ -267,7 +267,6 @@ UMAP_KEEP_VERSIONS = env.int("UMAP_KEEP_VERSIONS", default=10)
SITE_URL = env("SITE_URL", default="http://umap.org")
SHORT_SITE_URL = env("SHORT_SITE_URL", default=None)
SITE_NAME = "uMap"
SITE_DESCRIPTION = "Online map creator"
UMAP_DEMO_SITE = env("UMAP_DEMO_SITE", default=False)
UMAP_EXCLUDE_DEFAULT_MAPS = False
UMAP_MAPS_PER_PAGE = 5
@ -306,7 +305,6 @@ LOGIN_URL = "login"
SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/login/popup/end/"
AUTHENTICATION_BACKENDS = ()
DEPRECATED_AUTHENTICATION_BACKENDS = []
SOCIAL_AUTH_OPENSTREETMAP_OAUTH2_KEY = env(
"SOCIAL_AUTH_OPENSTREETMAP_OAUTH2_KEY", default=""

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -41,14 +41,33 @@ body.login header {
display: inline-block;
}
.login-grid span,
.login-grid a {
border: 1px solid #e5e5e5;
padding: 5px;
color: #000;
background-position: center bottom;
background-repeat: no-repeat;
background-size: 92px 92px;
height: 92px;
width: 92px;
margin-inline-end: 10px;
}
.login-grid .login-github {
background-image: url("./github.png");
}
.login-grid .login-bitbucket {
background-image: url("./bitbucket.png");
}
.login-grid .login-twitter-oauth2 {
background-image: url("./twitter.png");
}
.login-grid .login-openstreetmap,
.login-grid .login-openstreetmap-oauth2 {
background-image: url("./openstreetmap.png");
}
.login-grid .login-keycloak {
background-image: url("./keycloak.png");
}
/* **************************** */
/* home */

BIN
umap/static/umap/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

View file

@ -1191,13 +1191,11 @@ Fields.TernaryChoices = class extends Fields.MultiChoice {
Fields.NullableChoices = class extends Fields.TernaryChoices {
getChoices() {
return (
this.properties.choices || [
[true, translate('always')],
[false, translate('never')],
['null', translate('hidden')],
]
)
return [
[true, translate('always')],
[false, translate('never')],
['null', translate('hidden')],
]
}
}

View file

@ -447,11 +447,6 @@ export const SCHEMA = {
label: translate('Display label'),
inheritable: true,
default: false,
choices: [
[true, translate('always')],
[false, translate('never')],
['null', translate('on hover')],
],
},
slideshow: {
type: Object,

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% blocktranslate %}{{ current_user }}s maps{% endblocktranslate %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
<div class="col wide">
<h2 class="section">

View file

@ -1,10 +1,7 @@
{% extends "umap/content.html" %}
{% load i18n static %}
{% load i18n %}
{% block head_title %}
{% translate "My Profile" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="profile" %}
<div class="wrapper">
@ -31,10 +28,8 @@
</h3>
<ul>
{% for name in providers %}
<li class="login-grid">
{% with "umap/img/providers/"|add:name|add:".png" as path %}
<img src="{% static path %}" width="92px" height="92px" alt="{{ name }}" />
{% endwith %}
<li>
{{ name|title }}
</li>
{% endfor %}
</ul>
@ -51,7 +46,9 @@
{% for name in backends.backends %}
{% if name not in providers %}
<li>
{% include "umap/components/provider.html" with name=name %}
<a href="{% url "social:begin" name %}"
class="umap-login-popup login-{{ name }}"
title="{{ name|title }}"></a>
</li>
{% endif %}
{% endfor %}

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% blocktranslate %}{{ current_user }}s starred maps{% endblocktranslate %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
<div class="col wide">
<h2 class="section">

View file

@ -5,7 +5,7 @@
<head>
<title>
{% block head_title %}
{{ SITE_NAME }} - {{ SITE_DESCRIPTION }}
{{ SITE_NAME }}
{% endblock head_title %}
</title>
<meta charset="utf-8">

View file

@ -3,7 +3,7 @@
{% load i18n %}
{% block head_title %}
{% trans "Login" %} - {{ SITE_DESCRIPTION }}
{% trans "Login" %}
{% endblock head_title %}
{% load umap_tags i18n %}
@ -55,7 +55,10 @@
<ul class="login-grid block-grid">
{% for name in backends.backends %}
<li>
{% include "umap/components/provider.html" with name=name %}
<a rel="nofollow"
href="{% url "social:begin" name %}"
class="umap-login-popup login-{{ name }}"
title="{{ name|title }}"></a>
</li>
{% endfor %}
</ul>

View file

@ -1,9 +1,4 @@
{% extends "umap/content.html" %}
{% load i18n %}
{% block head_title %}
{% translate "About" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
{% include "umap/about_summary.html" %}

View file

@ -1,8 +0,0 @@
{% load static %}
<a href="{% url "social:begin" name %}"
class="umap-login-popup"
title="{{ name|title }}">
{% with "umap/img/providers/"|add:name|add:".png" as path %}
<img src="{% static path %}" width="92px" height="92px" alt="{{ name }}" />
{% endwith %}
</a>

View file

@ -3,7 +3,7 @@
{% load umap_tags i18n %}
{% block head_title %}
{{ map.name }} - {{ SITE_NAME }} - {{ SITE_DESCRIPTION }}
{{ map.name }} - {{ SITE_NAME }}
{% endblock head_title %}
{% block body_class %}
map_detail

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "Password change" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block content %}
<h2 class="section">
{% trans "Password change" %}

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "Password change successful" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block content %}
<h2 class="section">
{% trans "Password change successful" %}

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "Explore maps" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block messages %}
{# We don't want maps from the results list to display errors in the main page. #}
{% endblock messages %}

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "Team deletion" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="teams" %}
<div class="wrapper">

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% blocktranslate %}{{ current_team }}s maps{% endblocktranslate %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
<div class="wrapper">
<div class="row">

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "Create or edit a team" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="teams" %}
<div class="wrapper">

View file

@ -3,7 +3,7 @@
{% load i18n static %}
{% block head_title %}
{% translate "My Dashboard" %} - {{ SITE_DESCRIPTION }}
{{ SITE_NAME }} - {% trans "My Dashboard" %}
{% endblock head_title %}
{% block maincontent %}
{% trans "Search my maps" as placeholder %}

View file

@ -2,10 +2,6 @@
{% load i18n %}
{% block head_title %}
{% translate "My Teams" %} - {{ SITE_DESCRIPTION }}
{% endblock head_title %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="teams" %}
<div class="wrapper">

View file

@ -8,7 +8,7 @@ from umap.models import Map
def test_page_title(page, live_server):
page.goto(live_server.url)
expect(page).to_have_title("uMap - Online map creator")
expect(page).to_have_title("uMap")
@pytest.mark.parametrize(
@ -83,7 +83,7 @@ def test_login_from_map_page(live_server, page, tilelayer, settings, user, conte
page.get_by_role("button", name="Save").click()
assert Map.objects.count() == 0
login_page = login_page_info.value
expect(login_page).to_have_title("Login - Online map creator")
expect(login_page).to_have_title("Login")
login_page.get_by_placeholder("Username").fill(user.username)
login_page.get_by_placeholder("Password").fill("123123")
with page.expect_response(re.compile(r".*/map/create/")):

View file

@ -158,14 +158,11 @@ def test_should_not_be_possible_to_update_with_wrong_map_id_in_url(
def test_delete(client, datalayer, map):
assert map.datalayers.count() == 1
url = reverse("datalayer_delete", args=(map.pk, datalayer.pk))
client.login(username=map.owner.username, password="123123")
response = client.post(url, {}, follow=True)
assert response.status_code == 200
assert DataLayer.objects.filter(pk=datalayer.pk).count()
assert map.datalayers.count() == 0
assert DataLayer.objects.get(pk=datalayer.pk).share_status == DataLayer.DELETED
assert not DataLayer.objects.filter(pk=datalayer.pk).count()
# Check that map has not been impacted
assert Map.objects.filter(pk=map.pk).exists()
# Test response is a json

View file

@ -4,17 +4,15 @@ from unittest import mock
import pytest
from django.core.management import call_command
from umap.models import DataLayer, Map
from umap.models import Map
from .base import DataLayerFactory, MapFactory
from .base import MapFactory
pytestmark = pytest.mark.django_db
def test_empty_trash(user):
recent = MapFactory(owner=user)
recent_layer = DataLayerFactory(map=recent)
deleted_layer = DataLayerFactory(map=recent)
recent_deleted = MapFactory(owner=user)
recent_deleted.move_to_trash()
recent_deleted.save()
@ -22,20 +20,15 @@ def test_empty_trash(user):
mocked.return_value = datetime.utcnow() - timedelta(days=8)
old_deleted = MapFactory(owner=user)
old_deleted.move_to_trash()
deleted_layer.move_to_trash()
old_deleted.save()
old = MapFactory(owner=user)
assert Map.objects.count() == 4
assert DataLayer.objects.count() == 2
call_command("empty_trash", "--days=7", "--dry-run")
assert Map.objects.count() == 4
assert DataLayer.objects.count() == 2
call_command("empty_trash", "--days=9")
assert Map.objects.count() == 4
assert DataLayer.objects.count() == 2
call_command("empty_trash", "--days=7")
assert not Map.objects.filter(pk=old_deleted.pk)
assert Map.objects.filter(pk=old.pk)
assert Map.objects.filter(pk=recent.pk)
assert Map.objects.filter(pk=recent_deleted.pk)
assert not DataLayer.objects.filter(pk=deleted_layer.pk)
assert DataLayer.objects.filter(pk=recent_layer.pk)

View file

@ -810,17 +810,6 @@ def test_oembed_shared_status_map(client, map, datalayer, share_status):
assert response.status_code == 403
def test_download_does_not_include_delete_datalayers(client, map, datalayer):
datalayer.share_status = DataLayer.DELETED
datalayer.save()
url = reverse("map_download", args=(map.pk,))
response = client.get(url)
assert response.status_code == 200
# Test response is a json
j = json.loads(response.content.decode())
assert j["layers"] == []
def test_oembed_no_url_map(client, map, datalayer):
url = reverse("map_oembed")
response = client.get(url)

View file

@ -15,7 +15,7 @@ from urllib.request import Request, build_opener
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import BACKEND_SESSION_KEY, get_user_model
from django.contrib.auth import get_user_model
from django.contrib.auth import logout as do_logout
from django.contrib.gis.measure import D
from django.contrib.postgres.search import SearchQuery, SearchVector
@ -742,14 +742,14 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
def get_datalayers(self):
# When initializing datalayers from map, we cannot get the reference version
# the normal way, which is from the header X-Reference-Version
return [dl.metadata(self.request) for dl in self.object.datalayers]
return [dl.metadata(self.request) for dl in self.object.datalayer_set.all()]
@property
def edit_mode(self):
edit_mode = "disabled"
if self.object.can_edit(self.request):
edit_mode = "advanced"
elif any(d.can_edit(self.request) for d in self.object.datalayers):
elif any(d.can_edit(self.request) for d in self.object.datalayer_set.all()):
edit_mode = "simple"
return edit_mode
@ -1325,7 +1325,7 @@ class DataLayerDelete(DeleteView):
self.object = self.get_object()
if self.object.map != self.kwargs["map_inst"]:
return HttpResponseForbidden()
self.object.move_to_trash()
self.object.delete()
return simple_json_response(info=_("Layer successfully deleted."))
@ -1419,18 +1419,3 @@ class LoginPopupEnd(TemplateView):
"""
template_name = "umap/login_popup_end.html"
def get(self, *args, **kwargs):
backend = self.request.session[BACKEND_SESSION_KEY]
if backend in settings.DEPRECATED_AUTHENTICATION_BACKENDS:
name = backend.split(".")[-1]
messages.error(
self.request,
_(
"Using “%(name)s” to authenticate is deprecated. "
"Please configure another provider in your profile page."
)
% {"name": name},
)
return HttpResponseRedirect(reverse("user_profile"))
return super().get(*args, **kwargs)