rozbudowa wykresow o kategorie i usuniecie dupliakcji kodu z apnelu admina
This commit is contained in:
142
app.py
142
app.py
@@ -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
|
||||
|
Reference in New Issue
Block a user