Provide a link to delete maps from user’s dashboard

This commit is contained in:
David Larlet 2024-01-09 16:19:53 -05:00
parent 46cf432eb4
commit 5d69d3c22f
No known key found for this signature in database
GPG key ID: 3E2953A359E7E7BD
6 changed files with 148 additions and 62 deletions

View file

@ -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})

View file

@ -3,7 +3,7 @@
{% block maincontent %}
<div class="col wide">
<h2 class="section tabs">
<a href="{% url "user_dashboard" %}">{% trans "My Dashboard" %}</a> | <a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
<a href="{% url "user_dashboard" %}">{% trans "My Maps" %}</a> | <a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
</h2>
</div>
<div class="wrapper">

View file

@ -37,7 +37,8 @@
<td>
<a href="{{ map_inst.get_absolute_url }}?share">{% translate "Share" %}</a> |
<a href="{{ map_inst.get_absolute_url }}?edit">{% translate "Edit" %}</a> |
<a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a>
<a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a> |
<a href="{% url 'map_delete' map_inst.pk %}">{% translate "Delete" %}</a>
</td>
</tr>
{% endwith %}
@ -45,37 +46,75 @@
</tbody>
<tfoot>
<tr>
<td colspan="4"></td>
<td colspan="2">
<a href="{% url 'user_download' %}?p={{ request.GET.p }}" class="button">{% translate "Download all these maps" %}</a>
<td colspan="5"></td>
<td>
<a href="{% url 'user_download' %}?{% spaceless %}
{% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %}
{% endspaceless %}" class="button"
>{% blocktranslate count counter=maps.object_list|length %}
Download this map
{% plural %}
Download these {{ counter }} maps
{% endblocktranslate %}
</a>
</td>
</tr>
</tfoot>
</table>
<div class="pagination">
{% if maps.has_previous %}
<a href="?p=1{% if q %}&q={{ q }}{% endif %}">« {% translate "first" %}</a>
<a href="?p={{ maps.previous_page_number }}{% if q %}&q={{ q }}{% endif %}"> {% translate "previous" %}</a>
{% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %}
{% if maps.has_other_pages %}
<div class="pagination">
{% if maps.has_previous %}
<a href="?p=1{% if q %}&q={{ q }}{% endif %}">« {% translate "first" %}</a>
<a href="?p={{ maps.previous_page_number }}{% if q %}&q={{ q }}{% endif %}"> {% translate "previous" %}</a>
{% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %}
<span class="current">
{% blocktranslate with maps_number=maps.number num_pages=maps.paginator.num_pages %}
Page {{ maps_number }} of {{ num_pages }}
{% endblocktranslate %}
</span>
<span class="current">
{% blocktranslate with maps_number=maps.number num_pages=maps.paginator.num_pages %}
Page {{ maps_number }} of {{ num_pages }}
{% endblocktranslate %}
</span>
{% if maps.has_next %}
<a href="?p={{ maps.next_page_number }}{% if q %}&q={{ q }}{% endif %}">{% translate "next" %} </a>
<a href="?p={{ maps.paginator.num_pages }}{% if q %}&q={{ q }}{% endif %}">{% translate "last" %} »</a>
{% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %}
</div>
{% if maps.has_next %}
<a href="?p={{ maps.next_page_number }}{% if q %}&q={{ q }}{% endif %}">{% translate "next" %} </a>
<a href="?p={{ maps.paginator.num_pages }}{% if q %}&q={{ q }}{% endif %}">{% translate "last" %} »</a>
{% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %}
</div>
{% endif %}
<script type="text/javascript">
!(function () {
for (const deleteLink of document.querySelectorAll("table.maps a[href$='delete/']")) {
deleteLink.addEventListener('click', (event) => {
event.preventDefault()
const token = document.cookie.replace(
/(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/,
'$1'
)
if (confirm(L._('Are you sure you want to delete this map?'))) {
fetch(deleteLink.href, {
method: 'POST',
headers: {
'X-CSRFToken': token
},
})
.then((response) => {
if (response.ok) {
window.location.reload()
}
throw response
})
.catch((error) => console.debug(error))
}
})
}
})()
</script>

View file

@ -7,7 +7,9 @@
{% trans "Search my maps" as placeholder %}
<div class="col wide">
<h2 class="section tabs">
<a class="selected" href="{% url 'user_dashboard' %}">{% trans "My Dashboard" %}</a> | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
<a class="selected" href="{% url 'user_dashboard' %}"
>{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
</a> | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
</h2>
{% include "umap/search_bar.html" with action=request.get_full_path placeholder=placeholder %}
</div>

View file

@ -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

View file

@ -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"'