zmiany w funkcja oraz UX

This commit is contained in:
Mateusz Gruszczyński
2025-08-18 22:35:13 +02:00
parent fc108bceb5
commit a92d91c1dd
12 changed files with 327 additions and 274 deletions

139
app.py
View File

@@ -284,6 +284,7 @@ class Receipt(db.Model):
filesize = db.Column(db.Integer, nullable=True)
file_hash = db.Column(db.String(64), nullable=True, unique=True)
uploaded_by = db.Column(db.Integer, db.ForeignKey("user.id"))
version_token = db.Column(db.String(32), nullable=True)
shopping_list = db.relationship("ShoppingList", back_populates="receipts")
uploaded_by_user = db.relationship("User", backref="uploaded_receipts")
@@ -408,6 +409,8 @@ app.register_blueprint(static_bp)
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
def generate_version_token():
return secrets.token_hex(8)
def get_list_details(list_id):
shopping_list = ShoppingList.query.options(
@@ -419,9 +422,9 @@ def get_list_details(list_id):
items = sorted(shopping_list.items, key=lambda i: i.position or 0)
expenses = shopping_list.expenses
total_expense = sum(e.amount for e in expenses) if expenses else 0
receipt_files = [r.filename for r in shopping_list.receipts]
receipts = shopping_list.receipts
return shopping_list, items, receipt_files, expenses, total_expense
return shopping_list, items, receipts, expenses, total_expense
def get_total_expense_for_list(list_id, start_date=None, end_date=None):
@@ -505,7 +508,8 @@ def save_resized_image(file, path):
image.info.clear()
new_path = path.rsplit(".", 1)[0] + ".webp"
image.save(new_path, **WEBP_SAVE_PARAMS)
#image.save(new_path, **WEBP_SAVE_PARAMS)
image.save(new_path, format="WEBP", method=6, quality=100)
except Exception as e:
raise ValueError(f"Błąd podczas przetwarzania obrazu: {e}")
@@ -569,23 +573,27 @@ def receipt_error(message):
def rotate_receipt_by_id(receipt_id):
receipt = Receipt.query.get_or_404(receipt_id)
old_path = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
path = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
if not os.path.exists(old_path):
if not os.path.exists(path):
raise FileNotFoundError("Plik nie istnieje")
image = Image.open(old_path)
rotated = image.rotate(-90, expand=True)
try:
image = Image.open(path)
rotated = image.rotate(-90, expand=True)
new_filename = generate_new_receipt_filename(receipt.list_id)
new_path = os.path.join(app.config["UPLOAD_FOLDER"], new_filename)
rotated.save(new_path, **WEBP_SAVE_PARAMS)
rotated = rotated.convert("RGB")
rotated.info.clear()
os.remove(old_path)
receipt.filename = new_filename
db.session.commit()
rotated.save(path, format="WEBP", method=6, quality=100)
receipt.version_token = generate_version_token()
recalculate_filesizes(receipt.id)
db.session.commit()
return receipt
return receipt
except Exception as e:
app.logger.exception("Błąd podczas rotacji pliku")
raise RuntimeError(f"Błąd podczas rotacji pliku: {e}")
def delete_receipt_by_id(receipt_id):
@@ -610,23 +618,18 @@ def handle_crop_receipt(receipt_id, file):
if not receipt_id or not file:
return {"success": False, "error": "Brak danych"}
receipt = Receipt.query.get_or_404(receipt_id)
old_path = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
try:
new_filename = generate_new_receipt_filename(receipt.list_id)
new_path = os.path.join(app.config["UPLOAD_FOLDER"], new_filename)
receipt = Receipt.query.get_or_404(receipt_id)
path = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
save_resized_image(file, new_path)
if os.path.exists(old_path):
os.remove(old_path)
receipt.filename = os.path.basename(new_path)
db.session.commit()
save_resized_image(file, path)
receipt.version_token = generate_version_token()
recalculate_filesizes(receipt.id)
db.session.commit()
return {"success": True}
except Exception as e:
app.logger.exception("Błąd podczas przycinania paragonu")
return {"success": False, "error": str(e)}
@@ -1760,7 +1763,7 @@ def view_list(list_id):
flash("W celu modyfikacji listy, przejdź do panelu administracyjnego.", "info")
return redirect(url_for("shared_list", token=shopping_list.share_token))
shopping_list, items, receipt_files, expenses, total_expense = get_list_details(
shopping_list, items, receipts, expenses, total_expense = get_list_details(
list_id
)
total_count = len(items)
@@ -1785,7 +1788,7 @@ def view_list(list_id):
"list.html",
list=shopping_list,
items=items,
receipt_files=receipt_files,
receipts=receipts,
total_count=total_count,
purchased_count=purchased_count,
percent=percent,
@@ -1960,7 +1963,7 @@ def shared_list(token=None, list_id=None):
list_id = shopping_list.id
total_expense = get_total_expense_for_list(list_id)
shopping_list, items, receipt_files, expenses, total_expense = get_list_details(
shopping_list, items, receipts, expenses, total_expense = get_list_details(
list_id
)
@@ -1981,7 +1984,7 @@ def shared_list(token=None, list_id=None):
"list_share.html",
list=shopping_list,
items=items,
receipt_files=receipt_files,
receipts=receipts,
expenses=expenses,
total_expense=total_expense,
is_share=True,
@@ -2130,66 +2133,60 @@ def all_products():
@app.route("/upload_receipt/<int:list_id>", methods=["POST"])
@login_required
def upload_receipt(list_id):
l = db.session.get(ShoppingList, list_id)
if "receipt" not in request.files:
return receipt_error("Brak pliku")
file = request.files["receipt"]
if file.filename == "":
file = request.files.get("receipt")
if not file or file.filename == "":
return receipt_error("Nie wybrano pliku")
if file and allowed_file(file.filename):
file_bytes = file.read()
file.seek(0)
file_hash = hashlib.sha256(file_bytes).hexdigest()
if not allowed_file(file.filename):
return receipt_error("Niedozwolony format pliku")
existing = Receipt.query.filter_by(file_hash=file_hash).first()
if existing:
return receipt_error("Taki plik już istnieje")
file_bytes = file.read()
file.seek(0)
file_hash = hashlib.sha256(file_bytes).hexdigest()
now = datetime.now(timezone.utc)
timestamp = now.strftime("%Y%m%d_%H%M")
random_part = secrets.token_hex(3)
webp_filename = f"list_{list_id}_{timestamp}_{random_part}.webp"
file_path = os.path.join(app.config["UPLOAD_FOLDER"], webp_filename)
existing = Receipt.query.filter_by(file_hash=file_hash).first()
if existing:
return receipt_error("Taki plik już istnieje")
try:
if file.filename.lower().endswith(".pdf"):
file.seek(0)
save_pdf_as_webp(file, file_path)
else:
save_resized_image(file, file_path)
except ValueError as e:
return receipt_error(str(e))
now = datetime.now(timezone.utc)
timestamp = now.strftime("%Y%m%d_%H%M")
random_part = secrets.token_hex(3)
webp_filename = f"list_{list_id}_{timestamp}_{random_part}.webp"
file_path = os.path.join(app.config["UPLOAD_FOLDER"], webp_filename)
filesize = os.path.getsize(file_path)
uploaded_at = datetime.now(timezone.utc)
try:
if file.filename.lower().endswith(".pdf"):
file.seek(0)
save_pdf_as_webp(file, file_path)
else:
save_resized_image(file, file_path)
except ValueError as e:
return receipt_error(str(e))
try:
new_receipt = Receipt(
list_id=list_id,
filename=webp_filename,
filesize=filesize,
uploaded_at=uploaded_at,
filesize=os.path.getsize(file_path),
uploaded_at=now,
file_hash=file_hash,
uploaded_by=current_user.id,
version_token=generate_version_token(),
)
db.session.add(new_receipt)
db.session.commit()
except Exception as e:
return receipt_error(f"Błąd zapisu do bazy: {str(e)}")
if (
request.is_json
or request.headers.get("X-Requested-With") == "XMLHttpRequest"
):
url = url_for("uploaded_file", filename=webp_filename)
socketio.emit("receipt_added", {"url": url}, to=str(list_id))
return jsonify({"success": True, "url": url})
if request.is_json or request.headers.get("X-Requested-With") == "XMLHttpRequest":
url = url_for("uploaded_file", filename=webp_filename) + f"?v={new_receipt.version_token or '0'}"
socketio.emit("receipt_added", {"url": url}, to=str(list_id))
return jsonify({"success": True, "url": url})
flash("Wgrano paragon", "success")
return redirect(request.referrer or url_for("main_page"))
return receipt_error("Niedozwolony format pliku")
flash("Wgrano paragon", "success")
return redirect(request.referrer or url_for("main_page"))
@app.route("/uploads/<filename>")