rozbudowa wykresow o kategorie i usuniecie dupliakcji kodu z apnelu admina

This commit is contained in:
Mateusz Gruszczyński
2025-08-01 11:31:17 +02:00
parent 2df64bbe2e
commit cfae8571de
11 changed files with 358 additions and 451 deletions

142
app.py
View File

@@ -50,7 +50,7 @@ from collections import defaultdict, deque
from functools import wraps
from flask_talisman import Talisman
from flask_session import Session
from types import SimpleNamespace
# OCR
import pytesseract
@@ -345,7 +345,7 @@ with app.app_context():
db.session.add_all(Category(name=cat) for cat in missing)
db.session.commit()
print(f"[INFO] Dodano brakujące kategorie: {', '.join(missing)}")
#else:
# else:
# print("[INFO] Wszystkie domyślne kategorie już istnieją")
@@ -357,7 +357,7 @@ def serve_js(filename):
# response.cache_control.must_revalidate = True
response.headers["Cache-Control"] = app.config["JS_CACHE_CONTROL"]
response.headers.pop("Content-Disposition", None)
#response.headers.pop("Etag", None)
# response.headers.pop("Etag", None)
return response
@@ -366,7 +366,7 @@ def serve_css(filename):
response = send_from_directory("static/css", filename)
response.headers["Cache-Control"] = app.config["CSS_CACHE_CONTROL"]
response.headers.pop("Content-Disposition", None)
#response.headers.pop("Etag", None)
# response.headers.pop("Etag", None)
return response
@@ -375,7 +375,7 @@ def serve_js_lib(filename):
response = send_from_directory("static/lib/js", filename)
response.headers["Cache-Control"] = app.config["LIB_JS_CACHE_CONTROL"]
response.headers.pop("Content-Disposition", None)
#response.headers.pop("Etag", None)
# response.headers.pop("Etag", None)
return response
@@ -384,7 +384,7 @@ def serve_css_lib(filename):
response = send_from_directory("static/lib/css", filename)
response.headers["Cache-Control"] = app.config["LIB_CSS_CACHE_CONTROL"]
response.headers.pop("Content-Disposition", None)
#response.headers.pop("Etag", None)
# response.headers.pop("Etag", None)
return response
@@ -637,17 +637,45 @@ def get_total_expenses_grouped_by_list_created_at(
lists_query = lists_query.filter(ShoppingList.owner_id == user_id)
if category_id:
lists_query = lists_query.join(
shopping_list_category,
shopping_list_category.c.shopping_list_id == ShoppingList.id,
).filter(shopping_list_category.c.category_id == category_id)
if str(category_id) == "none": # Bez kategorii
lists_query = lists_query.filter(~ShoppingList.categories.any())
else:
try:
cat_id_int = int(category_id)
except ValueError:
return {"labels": [], "expenses": []}
lists_query = lists_query.join(
shopping_list_category,
shopping_list_category.c.shopping_list_id == ShoppingList.id,
).filter(shopping_list_category.c.category_id == cat_id_int)
# Obsługa nowych zakresów
today = datetime.now(timezone.utc).date()
if range_type == "last30days":
dt_start = today - timedelta(days=29)
dt_end = today + timedelta(days=1)
start_date, end_date = dt_start.strftime("%Y-%m-%d"), dt_end.strftime(
"%Y-%m-%d"
)
elif range_type == "currentmonth":
dt_start = today.replace(day=1)
dt_end = today + timedelta(days=1)
start_date, end_date = dt_start.strftime("%Y-%m-%d"), dt_end.strftime(
"%Y-%m-%d"
)
if start_date and end_date:
try:
dt_start = datetime.strptime(start_date, "%Y-%m-%d")
dt_end = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
dt_end = datetime.strptime(end_date, "%Y-%m-%d")
if dt_end.tzinfo is None:
dt_end = dt_end.replace(tzinfo=timezone.utc)
dt_end += timedelta(days=1)
except Exception:
return {"error": "Błędne daty", "labels": [], "expenses": []}
lists_query = lists_query.filter(
ShoppingList.created_at >= dt_start, ShoppingList.created_at < dt_end
)
@@ -658,7 +686,6 @@ def get_total_expenses_grouped_by_list_created_at(
list_ids = [l.id for l in lists]
# Suma wszystkich wydatków dla każdej listy
total_expenses = (
db.session.query(
Expense.list_id, func.sum(Expense.amount).label("total_amount")
@@ -674,7 +701,9 @@ def get_total_expenses_grouped_by_list_created_at(
for sl in lists:
if sl.id in expense_map:
ts = sl.created_at or datetime.now(timezone.utc)
if range_type == "monthly":
if range_type in ("last30days", "currentmonth"):
key = ts.strftime("%Y-%m-%d") # dzienny widok
elif range_type == "monthly":
key = ts.strftime("%Y-%m")
elif range_type == "quarterly":
key = f"{ts.year}-Q{((ts.month - 1) // 3 + 1)}"
@@ -794,18 +823,19 @@ def get_admin_expense_summary():
return f"#{r:02x}{g:02x}{b:02x}"
"""
def category_to_color(name):
hash_val = int(hashlib.md5(name.encode("utf-8")).hexdigest(), 16)
hue = (hash_val % 360) / 360.0
saturation = 0.60 + ((hash_val >> 8) % 17) / 100.0
lightness = 0.28 + ((hash_val >> 16) % 11) / 100.0
lightness = 0.28 + ((hash_val >> 16) % 11) / 100.0
r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation)
return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"
def get_total_expenses_grouped_by_category(
show_all, range_type, start_date, end_date, user_id
show_all, range_type, start_date, end_date, user_id, category_id=None
):
lists_query = ShoppingList.query
@@ -816,6 +846,19 @@ def get_total_expenses_grouped_by_category(
else:
lists_query = lists_query.filter(ShoppingList.owner_id == user_id)
if category_id:
if str(category_id) == "none": # Bez kategorii
lists_query = lists_query.filter(~ShoppingList.categories.any())
else:
try:
cat_id_int = int(category_id)
except ValueError:
return {"labels": [], "datasets": []}
lists_query = lists_query.join(
shopping_list_category,
shopping_list_category.c.shopping_list_id == ShoppingList.id,
).filter(shopping_list_category.c.category_id == cat_id_int)
if start_date and end_date:
try:
dt_start = datetime.strptime(start_date, "%Y-%m-%d")
@@ -856,10 +899,19 @@ def get_total_expenses_grouped_by_category(
all_labels.add(key)
# Specjalna obsługa dla filtra "Bez kategorii"
if str(category_id) == "none":
if not l.categories:
data_map[key]["Bez kategorii"] += total_expense
continue # 🔹 Pomijamy dalsze dodawanie innych kategorii
# Standardowa logika
if not l.categories:
data_map[key]["Inne"] += total_expense
data_map[key]["Bez kategorii"] += total_expense
else:
for c in l.categories:
if category_id and str(c.id) != str(category_id):
continue
data_map[key][c.name] += total_expense
labels = sorted(all_labels)
@@ -1121,9 +1173,13 @@ def log_request(response):
duration = round((time.time() - start) * 1000, 2) if start else "-"
agent = request.headers.get("User-Agent", "-")
if status == 304:
app.logger.info(f"REVALIDATED: {ip} - \"{method} {path}\" {status} {length} {duration}ms \"{agent}\"")
app.logger.info(
f'REVALIDATED: {ip} - "{method} {path}" {status} {length} {duration}ms "{agent}"'
)
else:
app.logger.info(f'{ip} - "{method} {path}" {status} {length} {duration}ms "{agent}"')
app.logger.info(
f'{ip} - "{method} {path}" {status} {length} {duration}ms "{agent}"'
)
app.logger.debug(f"Request headers: {dict(request.headers)}")
app.logger.debug(f"Response headers: {dict(response.headers)}")
return response
@@ -1600,9 +1656,9 @@ def view_list(list_id):
)
@app.route("/user_expenses")
@app.route("/expenses")
@login_required
def user_expenses():
def expenses():
start_date_str = request.args.get("start_date")
end_date_str = request.args.get("end_date")
category_id = request.args.get("category_id", type=int)
@@ -1631,6 +1687,8 @@ def user_expenses():
.all()
)
categories.append(SimpleNamespace(id="none", name="Bez kategorii"))
start = None
end = None
@@ -1650,10 +1708,13 @@ def user_expenses():
)
if category_id:
expenses_query = expenses_query.join(
shopping_list_category,
shopping_list_category.c.shopping_list_id == ShoppingList.id,
).filter(shopping_list_category.c.category_id == category_id)
if str(category_id) == "none": # Bez kategorii
lists_query = lists_query.filter(~ShoppingList.categories.any())
else:
lists_query = lists_query.join(
shopping_list_category,
shopping_list_category.c.shopping_list_id == ShoppingList.id,
).filter(shopping_list_category.c.category_id == category_id)
if start_date_str and end_date_str:
try:
@@ -1704,7 +1765,7 @@ def user_expenses():
]
return render_template(
"user_expenses.html",
"expenses.html",
expense_table=expense_table,
lists_data=lists_data,
categories=categories,
@@ -1713,14 +1774,14 @@ def user_expenses():
)
@app.route("/user_expenses_data")
@app.route("/expenses_data")
@login_required
def user_expenses_data():
def expenses_data():
range_type = request.args.get("range", "monthly")
start_date = request.args.get("start_date")
end_date = request.args.get("end_date")
show_all = request.args.get("show_all", "true").lower() == "true"
category_id = request.args.get("category_id", type=int)
category_id = request.args.get("category_id")
by_category = request.args.get("by_category", "false").lower() == "true"
if by_category:
@@ -1730,6 +1791,7 @@ def user_expenses_data():
start_date=start_date,
end_date=end_date,
user_id=current_user.id,
category_id=category_id,
)
else:
result = get_total_expenses_grouped_by_list_created_at(
@@ -2722,30 +2784,6 @@ def delete_suggestion_ajax(suggestion_id):
return jsonify({"success": True, "message": "Sugestia została usunięta."})
@app.route("/admin/expenses_data")
@login_required
def admin_expenses_data():
if not current_user.is_admin:
return jsonify({"error": "Brak uprawnień"}), 403
range_type = request.args.get("range", "monthly")
start_date = request.args.get("start_date")
end_date = request.args.get("end_date")
result = get_total_expenses_grouped_by_list_created_at(
user_only=False,
admin=True,
show_all=True,
range_type=range_type,
start_date=start_date,
end_date=end_date,
user_id=None,
)
if "error" in result:
return jsonify({"error": result["error"]}), 400
return jsonify(result)
@app.route("/admin/promote_user/<int:user_id>")
@login_required
@admin_required