Provide a link to delete maps from user’s dashboard
This commit is contained in:
parent
46cf432eb4
commit
5d69d3c22f
6 changed files with 148 additions and 62 deletions
|
@ -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})
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,13 +46,22 @@
|
|||
</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>
|
||||
{% if maps.has_other_pages %}
|
||||
<div class="pagination">
|
||||
{% if maps.has_previous %}
|
||||
<a href="?p=1{% if q %}&q={{ q }}{% endif %}">« {% translate "first" %}</a>
|
||||
|
@ -79,3 +89,32 @@
|
|||
{# 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"'
|
||||
|
|
Loading…
Reference in a new issue