aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/static/fietsboek.js20
-rw-r--r--fietsboek/templates/browse.jinja26
-rw-r--r--fietsboek/views/browse.py33
3 files changed, 47 insertions, 12 deletions
diff --git a/fietsboek/static/fietsboek.js b/fietsboek/static/fietsboek.js
index dd5b9bf..d27e21e 100644
--- a/fietsboek/static/fietsboek.js
+++ b/fietsboek/static/fietsboek.js
@@ -130,4 +130,24 @@ document.addEventListener('DOMContentLoaded', function(event) {
}, false)
})
+ /* Enable the "Download archive" button */
+ var button = $("#archiveDownloadButton");
+ if (button) {
+ button.addEventListener('click', () => {
+ let checked = document.querySelectorAll(".archive-checkbox:checked");
+ let url = new URL("/track/archive", window.location);
+ checked.forEach((c) => {
+ url.searchParams.append("track_id[]", c.value);
+ });
+ window.location.assign(url);
+ });
+ }
+
+ /* Enable checkbox listeners */
+ document.querySelectorAll(".archive-checkbox").forEach((c) => {
+ c.addEventListener("change", () => {
+ let checked = document.querySelectorAll(".archive-checkbox:checked");
+ $("#archiveDownloadButton").disabled = (checked.length == 0);
+ });
+ });
});
diff --git a/fietsboek/templates/browse.jinja2 b/fietsboek/templates/browse.jinja2
index 1f61bc6..2732984 100644
--- a/fietsboek/templates/browse.jinja2
+++ b/fietsboek/templates/browse.jinja2
@@ -2,10 +2,11 @@
{% block content %}
<div class="container">
<h1>{{ _("page.browse.title") }}</h1>
+ {% if tracks %}
{% for track in tracks %}
<div class="card mb-3">
<h5 class="card-header">
- <input type="checkbox" class="form-check-input" name="track_id[]" value="{{ track.id }}">
+ <input type="checkbox" class="form-check-input archive-checkbox" name="track_id[]" value="{{ track.id }}">
<a href="{{ request.route_url('details', track_id=track.id) }}">{{ track.title | default(track.date, true) }}</a>
{% if track.text_tags() %}
{% for tag in track.tags %}<span class="badge bg-info text-dark">{{ tag.tag }}</span> {% endfor %}
@@ -56,7 +57,8 @@
</div>
</div>
{% endfor %}
- {% if not tracks %}
+ <button type="button" class="btn btn-primary" id="archiveDownloadButton" disabled><i class="bi bi-file-earmark-zip"></i> {{ _("page.browse.download_multiple") }}</button>
+ {% else %}
<p>{{ _("page.browse.no_tracks") }}</p>
{% endif %}
</div>
diff --git a/fietsboek/views/browse.py b/fietsboek/views/browse.py
index 24cf082..490c0cf 100644
--- a/fietsboek/views/browse.py
+++ b/fietsboek/views/browse.py
@@ -1,5 +1,6 @@
"""Views for browsing all tracks."""
-from zipfile import ZipFile
+from io import RawIOBase
+from zipfile import ZipFile, ZIP_DEFLATED
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPForbidden
@@ -10,12 +11,20 @@ from sqlalchemy import select
from .. import models, util
-class Stream:
+class Stream(RawIOBase):
+ """A :class:`Stream` represents an in-memory buffered FIFO.
+
+ This is useful for the zipfile module, as it needs a file-like object, but
+ we do not want to create an actual temporary file.
+ """
+
def __init__(self):
+ super().__init__()
self.buffer = []
def write(self, b):
self.buffer.append(b)
+ return len(b)
def readall(self):
buf = self.buffer
@@ -71,10 +80,11 @@ def archive(request):
:return: The HTTP response.
:rtype: pyramid.response.Response
"""
- from pprint import pformat
+ # We need to create a separate session, otherwise we will get detached instances
+ session = request.registry['dbsession_factory']()
track_ids = set(map(int, request.params.getall("track_id[]")))
- tracks = request.dbsession.execute(
+ tracks = session.execute(
select(models.Track).filter(models.Track.id.in_(track_ids))).scalars().fetchall()
for track in tracks:
@@ -82,12 +92,15 @@ def archive(request):
return HTTPForbidden()
def generate():
- stream = Stream()
- with ZipFile(stream, "w") as zipfile:
- for track in tracks:
- zipfile.writestr(f"track_{track.id}.gpx", track.gpx_data)
- yield sream.readall()
- yield stream.readall()
+ try:
+ stream = Stream()
+ with ZipFile(stream, "w", ZIP_DEFLATED) as zipfile:
+ for track in tracks:
+ zipfile.writestr(f"track_{track.id}.gpx", track.gpx_data)
+ yield stream.readall()
+ yield stream.readall()
+ finally:
+ session.close()
return Response(
app_iter=generate(),