diff --git a/app.py b/app.py index c4eff22..6ede434 100644 --- a/app.py +++ b/app.py @@ -116,6 +116,14 @@ failed_login_attempts = defaultdict(deque) MAX_ATTEMPTS = 10 TIME_WINDOW = 60 * 60 +WEBP_SAVE_PARAMS = { + "format": "WEBP", + "lossless": True, # lub False jeśli chcesz używać quality + "method": 6, + # "quality": 95, # tylko jeśli lossless=False +} + + db = SQLAlchemy(app) socketio = SocketIO(app, async_mode="eventlet") login_manager = LoginManager(app) @@ -328,8 +336,7 @@ def save_resized_image(file, path): image.info.clear() new_path = path.rsplit(".", 1)[0] + ".webp" - # image.save(new_path, format="WEBP", quality=100, method=0) - image.save(new_path, format="WEBP", lossless=True, method=6) + image.save(new_path, **WEBP_SAVE_PARAMS) except Exception as e: raise ValueError(f"Błąd podczas przetwarzania obrazu: {e}") @@ -390,7 +397,7 @@ def rotate_receipt_by_id(receipt_id): new_filename = generate_new_receipt_filename(receipt.list_id) new_path = os.path.join(app.config["UPLOAD_FOLDER"], new_filename) - rotated.save(new_path, format="WEBP", quality=100) + rotated.save(new_path, **WEBP_SAVE_PARAMS) os.remove(old_path) receipt.filename = new_filename @@ -486,6 +493,35 @@ def get_expenses_aggregated_by_list_created_at( return {"labels": labels, "expenses": expenses} +def recalculate_filesizes(receipt_id: int = None): + updated = 0 + not_found = 0 + unchanged = 0 + + if receipt_id is not None: + receipt = db.session.get(Receipt, receipt_id) + receipts = [receipt] if receipt else [] + else: + receipts = db.session.execute(db.select(Receipt)).scalars().all() + + for r in receipts: + if not r: + continue + filepath = os.path.join(app.config["UPLOAD_FOLDER"], r.filename) + if os.path.exists(filepath): + real_size = os.path.getsize(filepath) + if r.filesize != real_size: + r.filesize = real_size + updated += 1 + else: + unchanged += 1 + else: + not_found += 1 + + db.session.commit() + return updated, unchanged, not_found + + ############# OCR ########################### @@ -1431,6 +1467,7 @@ def rotate_receipt_user(receipt_id): try: rotate_receipt_by_id(receipt_id) + recalculate_filesizes(receipt_id) flash("Obrócono paragon", "success") except FileNotFoundError: flash("Plik nie istnieje", "danger") @@ -1730,6 +1767,15 @@ def admin_receipts(id): try: if id == "all": receipts = Receipt.query.order_by(Receipt.uploaded_at.desc()).all() + + # Szukaj sierot tylko dla "all" + upload_folder = app.config["UPLOAD_FOLDER"] + all_db_filenames = set(r.filename for r in receipts) + files_on_disk = set(os.listdir(upload_folder)) + stale_files = [ + f for f in files_on_disk + if f.endswith(".webp") and f not in all_db_filenames and f.startswith("list_") + ] else: list_id = int(id) receipts = ( @@ -1737,20 +1783,11 @@ def admin_receipts(id): .order_by(Receipt.uploaded_at.desc()) .all() ) + stale_files = [] # brak sierot except ValueError: flash("Nieprawidłowe ID listy.", "danger") return redirect(url_for("admin_panel")) - # Przeszukaj folder upload pod kątem „sierot” - upload_folder = app.config["UPLOAD_FOLDER"] - db_filenames = set(r.filename for r in receipts) - all_db_filenames = set(r.filename for r in Receipt.query.all()) # Wszystko z bazy - files_on_disk = set(os.listdir(upload_folder)) - stale_files = [ - f for f in files_on_disk - if f.endswith(".webp") and f not in all_db_filenames and f.startswith("list_") - ] - # Przekaż do template: receipts (z bazy) i orphan_files (sieroty) return render_template( "admin/receipts.html", receipts=receipts, @@ -1759,34 +1796,13 @@ def admin_receipts(id): ) -@app.route("/admin/delete_orphan_receipt_file/") -@login_required -@admin_required -def delete_orphan_receipt_file(filename): - upload_folder = app.config["UPLOAD_FOLDER"] - safe_filename = os.path.basename(filename) - file_path = os.path.join(upload_folder, safe_filename) - # Dowolnego pliku nie kasujemy jeśli jest w bazie (Receipt.filename) - if Receipt.query.filter_by(filename=safe_filename).first(): - flash("Nie możesz usunąć pliku powiązanego z bazą!", "danger") - return redirect(url_for("admin_receipts", id="all")) - if not os.path.exists(file_path): - flash("Plik już nie istnieje.", "warning") - else: - try: - os.remove(file_path) - flash(f"Usunięto plik: {safe_filename}", "success") - except Exception as e: - flash(f"Błąd przy usuwaniu pliku: {e}", "danger") - return redirect(url_for("admin_receipts", id="all")) - - @app.route("/admin/rotate_receipt/") @login_required @admin_required def rotate_receipt(receipt_id): try: rotate_receipt_by_id(receipt_id) + recalculate_filesizes(receipt_id) flash("Obrócono paragon", "success") except FileNotFoundError: flash("Plik nie istnieje", "danger") @@ -1797,9 +1813,27 @@ def rotate_receipt(receipt_id): @app.route("/admin/delete_receipt/") +@app.route("/admin/delete_receipt/orphan/") @login_required @admin_required -def delete_receipt(receipt_id): +def delete_receipt(receipt_id=None, filename=None): + if filename: # tryb orphan + safe_filename = os.path.basename(filename) + if Receipt.query.filter_by(filename=safe_filename).first(): + flash("Nie można usunąć pliku powiązanego z bazą!", "danger") + else: + file_path = os.path.join(app.config["UPLOAD_FOLDER"], safe_filename) + if os.path.exists(file_path): + try: + os.remove(file_path) + flash(f"Usunięto plik: {safe_filename}", "success") + except Exception as e: + flash(f"Błąd przy usuwaniu pliku: {e}", "danger") + else: + flash("Plik już nie istnieje.", "warning") + return redirect(url_for("admin_receipts", id="all")) + + # tryb z rekordem w bazie try: delete_receipt_by_id(receipt_id) flash("Paragon usunięty", "success") @@ -1826,6 +1860,8 @@ def rename_receipt(receipt_id): try: os.rename(old_path, new_path) receipt.filename = new_filename + db.session.flush() + recalculate_filesizes(receipt.id) db.session.commit() flash("Zmieniono nazwę pliku", "success") except Exception as e: @@ -2215,7 +2251,7 @@ def crop_receipt(): receipt.filename = os.path.basename(new_path) db.session.commit() - + recalculate_filesizes(receipt.id) return jsonify(success=True) except Exception as e: return jsonify(success=False, error=str(e)) @@ -2224,25 +2260,8 @@ def crop_receipt(): @app.route("/admin/recalculate_filesizes") @login_required @admin_required -def recalculate_filesizes(): - updated = 0 - not_found = 0 - unchanged = 0 - - receipts = Receipt.query.all() - for r in receipts: - filepath = os.path.join(app.config["UPLOAD_FOLDER"], r.filename) - if os.path.exists(filepath): - real_size = os.path.getsize(filepath) - if r.filesize != real_size: - r.filesize = real_size - updated += 1 - else: - unchanged += 1 - else: - not_found += 1 - - db.session.commit() +def recalculate_filesizes_all(): + updated, unchanged, not_found = recalculate_filesizes() flash( f"Zaktualizowano: {updated}, bez zmian: {unchanged}, brak pliku: {not_found}", "success", diff --git a/templates/admin/receipts.html b/templates/admin/receipts.html index 5673415..a7affee 100644 --- a/templates/admin/receipts.html +++ b/templates/admin/receipts.html @@ -5,8 +5,8 @@ -{% if orphan_files %} -
-

🧐 Znalezione nieprzypisane pliki ({{ orphan_files_count }})

-
- {% for f in orphan_files %} -
-
- -
-

{{ f }}

-
Brak powiązania z listą!
- - 🗑 Usuń plik z serwera - -
-
+{% if orphan_files and request.path.endswith('/all') %} + +
+

Znalezione nieprzypisane pliki ({{ orphan_files_count }})

+
+ {% for f in orphan_files %} +
+
+ +
+

{{ f }}

+
Brak powiązania z listą!
+ + 🗑 Usuń plik z serwera +
- {% endfor %} +
+ {% endfor %} +
{% endif %}