From 5d69d3c22fc238c7f21526765676bc188fad8399 Mon Sep 17 00:00:00 2001 From: David Larlet Date: Tue, 9 Jan 2024 16:19:53 -0500 Subject: [PATCH] =?UTF-8?q?Provide=20a=20link=20to=20delete=20maps=20from?= =?UTF-8?q?=20user=E2=80=99s=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- umap/models.py | 12 +-- umap/templates/auth/user_form.html | 2 +- umap/templates/umap/map_table.html | 97 +++++++++++++++++-------- umap/templates/umap/user_dashboard.html | 4 +- umap/tests/test_map_views.py | 62 +++++++++++++++- umap/views.py | 33 +++------ 6 files changed, 148 insertions(+), 62 deletions(-) diff --git a/umap/models.py b/umap/models.py index 9b868bba..4bff5f75 100644 --- a/umap/models.py +++ b/umap/models.py @@ -223,10 +223,10 @@ class Map(NamedModel): ) return map_settings - def generate_geojson(self, request): - geojson = self.settings - geojson["type"] = "umap" - geojson["uri"] = request.build_absolute_uri(self.get_absolute_url()) + def generate_umapjson(self, request): + umapjson = self.settings + umapjson["type"] = "umap" + umapjson["uri"] = request.build_absolute_uri(self.get_absolute_url()) datalayers = [] for datalayer in self.datalayer_set.all(): with open(datalayer.geojson.path, "rb") as f: @@ -234,8 +234,8 @@ class Map(NamedModel): if datalayer.settings: layer["_umap_options"] = datalayer.settings datalayers.append(layer) - geojson["layers"] = datalayers - return geojson + umapjson["layers"] = datalayers + return umapjson def get_absolute_url(self): return reverse("map", kwargs={"slug": self.slug or "map", "map_id": self.pk}) diff --git a/umap/templates/auth/user_form.html b/umap/templates/auth/user_form.html index 088b3be5..8fca2454 100644 --- a/umap/templates/auth/user_form.html +++ b/umap/templates/auth/user_form.html @@ -3,7 +3,7 @@ {% block maincontent %}

- {% trans "My Dashboard" %} | {% trans "My Profile" %} + {% trans "My Maps" %} | {% trans "My Profile" %}

diff --git a/umap/templates/umap/map_table.html b/umap/templates/umap/map_table.html index 84b8c12b..cec4ceeb 100644 --- a/umap/templates/umap/map_table.html +++ b/umap/templates/umap/map_table.html @@ -37,7 +37,8 @@ {% translate "Share" %} | {% translate "Edit" %} | - {% translate "Download" %} + {% translate "Download" %} | + {% translate "Delete" %} {% endwith %} @@ -45,37 +46,75 @@ - - - {% translate "Download all these maps" %} + + + {% blocktranslate count counter=maps.object_list|length %} + Download this map + {% plural %} + Download these {{ counter }} maps + {% endblocktranslate %} + - +{% endif %} + diff --git a/umap/templates/umap/user_dashboard.html b/umap/templates/umap/user_dashboard.html index 6e5d4ac7..64c936c4 100644 --- a/umap/templates/umap/user_dashboard.html +++ b/umap/templates/umap/user_dashboard.html @@ -7,7 +7,9 @@ {% trans "Search my maps" as placeholder %} diff --git a/umap/tests/test_map_views.py b/umap/tests/test_map_views.py index 3b87fb0d..774234d0 100644 --- a/umap/tests/test_map_views.py +++ b/umap/tests/test_map_views.py @@ -1,4 +1,6 @@ import json +import zipfile +from io import BytesIO import pytest from django.contrib.auth import get_user_model @@ -8,7 +10,7 @@ from django.urls import reverse from umap.models import DataLayer, Map, Star -from .base import login_required +from .base import MapFactory, UserFactory, login_required pytestmark = pytest.mark.django_db User = get_user_model() @@ -656,6 +658,64 @@ def test_download(client, map, datalayer): ] +def test_download_multiple_maps(client, map, datalayer): + map.share_status = Map.PRIVATE + map.save() + another_map = MapFactory( + owner=map.owner, name="Another map", share_status=Map.PUBLIC + ) + client.login(username=map.owner.username, password="123123") + url = reverse("user_download") + response = client.get(f"{url}?map_id={map.id}&map_id={another_map.id}") + assert response.status_code == 200 + with zipfile.ZipFile(file=BytesIO(response.content), mode="r") as f: + assert len(f.infolist()) == 2 + assert f.infolist()[0].filename == f"umap_backup_test-map_{another_map.id}.umap" + assert f.infolist()[1].filename == f"umap_backup_test-map_{map.id}.umap" + with f.open(f.infolist()[1]) as umap_file: + umapjson = json.loads(umap_file.read().decode()) + assert list(umapjson.keys()) == [ + "type", + "geometry", + "properties", + "uri", + "layers", + ] + assert umapjson["type"] == "umap" + assert umapjson["uri"] == f"http://testserver/en/map/test-map_{map.id}" + + +def test_download_multiple_maps_unauthorized(client, map, datalayer): + map.share_status = Map.PRIVATE + map.save() + user1 = UserFactory(username="user1") + another_map = MapFactory(owner=user1, name="Another map", share_status=Map.PUBLIC) + client.login(username=map.owner.username, password="123123") + url = reverse("user_download") + response = client.get(f"{url}?map_id={map.id}&map_id={another_map.id}") + assert response.status_code == 200 + with zipfile.ZipFile(file=BytesIO(response.content), mode="r") as f: + assert len(f.infolist()) == 1 + assert f.infolist()[0].filename == f"umap_backup_test-map_{map.id}.umap" + + +def test_download_multiple_maps_editor(client, map, datalayer): + map.share_status = Map.PRIVATE + map.save() + user1 = UserFactory(username="user1") + another_map = MapFactory(owner=user1, name="Another map", share_status=Map.PUBLIC) + another_map.editors.add(map.owner) + another_map.save() + client.login(username=map.owner.username, password="123123") + url = reverse("user_download") + response = client.get(f"{url}?map_id={map.id}&map_id={another_map.id}") + assert response.status_code == 200 + with zipfile.ZipFile(file=BytesIO(response.content), mode="r") as f: + assert len(f.infolist()) == 2 + assert f.infolist()[0].filename == f"umap_backup_test-map_{another_map.id}.umap" + assert f.infolist()[1].filename == f"umap_backup_test-map_{map.id}.umap" + + @pytest.mark.parametrize("share_status", [Map.PRIVATE, Map.BLOCKED]) def test_download_shared_status_map(client, map, datalayer, share_status): map.share_status = share_status diff --git a/umap/views.py b/umap/views.py index 423308d5..0456f049 100644 --- a/umap/views.py +++ b/umap/views.py @@ -290,46 +290,31 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin): return qs.order_by("-modified_at") def get_context_data(self, **kwargs): - kwargs.update( - { - "q": self.request.GET.get("q"), - "maps": self.paginate( - self.get_maps(), settings.UMAP_MAPS_PER_PAGE_OWNER - ), - } - ) + page = self.paginate(self.get_maps(), settings.UMAP_MAPS_PER_PAGE_OWNER) + kwargs.update({"q": self.request.GET.get("q"), "maps": page}) return super().get_context_data(**kwargs) user_dashboard = UserDashboard.as_view() -class UserDownload(PaginatorMixin, DetailView, SearchMixin): +class UserDownload(DetailView, SearchMixin): model = User - 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_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() + qs = Map.objects.filter(id__in=self.request.GET.getlist("map_id")) qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object)) return qs.order_by("-modified_at") def render_to_response(self, context, *args, **kwargs): zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file: - for map_ in self.paginate(self.get_maps()): - map_geojson = map_.generate_geojson(self.request) - geojson_file = io.StringIO(json.dumps(map_geojson)) + for map_ in self.get_maps(): + umapjson = map_.generate_umapjson(self.request) + geojson_file = io.StringIO(json.dumps(umapjson)) file_name = f"umap_backup_{map_.slug}_{map_.pk}.umap" zip_file.writestr(file_name, geojson_file.getvalue()) @@ -674,8 +659,8 @@ class MapDownload(DetailView): return reverse("map_download", args=(self.object.pk,)) def render_to_response(self, context, *args, **kwargs): - geojson = self.object.generate_geojson(self.request) - response = simple_json_response(**geojson) + umapjson = self.object.generate_umapjson(self.request) + response = simple_json_response(**umapjson) response[ "Content-Disposition" ] = f'attachment; filename="umap_backup_{self.object.slug}.umap"'