Polish mobile shopping rows and stabilize backup archives
This commit is contained in:
+15
-3
@@ -52,13 +52,25 @@ def export_backup_archive(
|
|||||||
payload["tables"][table_name] = [dict(row) for row in rows]
|
payload["tables"][table_name] = [dict(row) for row in rows]
|
||||||
|
|
||||||
uploads_root = Path(upload_folder)
|
uploads_root = Path(upload_folder)
|
||||||
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
uploads_snapshot_dir = Path(tempfile.mkdtemp(prefix="nouri-backup-uploads-"))
|
||||||
archive.writestr("backup.json", json.dumps(payload, ensure_ascii=False, indent=2))
|
try:
|
||||||
if uploads_root.exists():
|
if uploads_root.exists():
|
||||||
for file_path in uploads_root.rglob("*"):
|
for file_path in uploads_root.rglob("*"):
|
||||||
if file_path.is_file():
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
relative_path = file_path.relative_to(uploads_root)
|
relative_path = file_path.relative_to(uploads_root)
|
||||||
|
snapshot_path = uploads_snapshot_dir / relative_path
|
||||||
|
snapshot_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(file_path, snapshot_path)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||||
|
archive.writestr("backup.json", json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
|
for file_path in uploads_snapshot_dir.rglob("*"):
|
||||||
|
if file_path.is_file():
|
||||||
|
relative_path = file_path.relative_to(uploads_snapshot_dir)
|
||||||
archive.write(file_path, f"uploads/{relative_path.as_posix()}")
|
archive.write(file_path, f"uploads/{relative_path.as_posix()}")
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(uploads_snapshot_dir, ignore_errors=True)
|
||||||
|
|
||||||
return archive_path, backup_name
|
return archive_path, backup_name
|
||||||
|
|
||||||
|
|||||||
@@ -1356,6 +1356,18 @@ h3 {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shopping-entry-check-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-check-mark {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.shopping-entry-close-form {
|
.shopping-entry-close-form {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -1415,24 +1427,68 @@ h3 {
|
|||||||
|
|
||||||
@media (max-width: 680px) {
|
@media (max-width: 680px) {
|
||||||
.shopping-entry-row {
|
.shopping-entry-row {
|
||||||
align-items: stretch;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shopping-entry-open {
|
.shopping-entry-open {
|
||||||
width: 100%;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-main {
|
||||||
|
grid-template-columns: 56px minmax(0, 1fr);
|
||||||
|
gap: 0.8rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-visual,
|
||||||
|
.shopping-entry-fallback {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-copy {
|
||||||
|
gap: 0.12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-copy strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-copy .muted {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shopping-entry-actions,
|
.shopping-entry-actions,
|
||||||
.shopping-entry-actions form,
|
.shopping-entry-actions form,
|
||||||
.shopping-entry-actions button,
|
.shopping-entry-actions button,
|
||||||
.shopping-entry-close-form {
|
.shopping-entry-close-form {
|
||||||
width: 100%;
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-check-button {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.75rem 0.9rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-check-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopping-entry-check-mark {
|
||||||
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shopping-entry-close {
|
.shopping-entry-close {
|
||||||
width: 100%;
|
width: 2.75rem;
|
||||||
border-radius: 18px;
|
height: 2.75rem;
|
||||||
|
min-width: 2.75rem;
|
||||||
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,10 @@
|
|||||||
<div class="shopping-entry-actions">
|
<div class="shopping-entry-actions">
|
||||||
<form method="post" action="{{ url_for('main.shopping_check', entry_id=entry.id) }}">
|
<form method="post" action="{{ url_for('main.shopping_check', entry_id=entry.id) }}">
|
||||||
{{ csrf_input() }}
|
{{ csrf_input() }}
|
||||||
<button type="submit">Eingekauft</button>
|
<button type="submit" class="shopping-entry-check-button">
|
||||||
|
<span class="shopping-entry-check-mark" aria-hidden="true">✔</span>
|
||||||
|
<span class="shopping-entry-check-label">Eingekauft</span>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if entry.can_edit %}
|
{% if entry.can_edit %}
|
||||||
|
|||||||
Reference in New Issue
Block a user