nowe funkcje i fixy

This commit is contained in:
Mateusz Gruszczyński
2025-09-30 21:47:13 +02:00
parent c2cf310f89
commit fa017ce290
2 changed files with 296 additions and 151 deletions

258
app.py
View File

@@ -170,7 +170,10 @@ def read_commit_and_date(filename="version.txt", root_path=None):
return date_str, commit
deploy_date, commit = read_commit_and_date("version.txt", root_path=os.path.dirname(__file__))
deploy_date, commit = read_commit_and_date(
"version.txt", root_path=os.path.dirname(__file__)
)
if not deploy_date:
deploy_date = datetime.now().strftime("%Y.%m.%d")
if not commit:
@@ -1159,19 +1162,37 @@ def save_pdf_as_webp(file, path):
def get_active_months_query(visible_lists_query=None):
if db.engine.name in ("sqlite",):
def month_expr(col): return func.strftime("%Y-%m", col)
def month_expr(col):
return func.strftime("%Y-%m", col)
elif db.engine.name in ("mysql", "mariadb"):
def month_expr(col): return func.date_format(col, "%Y-%m")
def month_expr(col):
return func.date_format(col, "%Y-%m")
else: # PostgreSQL
def month_expr(col): return func.to_char(col, "YYYY-MM")
def month_expr(col):
return func.to_char(col, "YYYY-MM")
if visible_lists_query is not None:
s = visible_lists_query.subquery()
month_sel = month_expr(s.c.created_at).label("month")
inner = db.session.query(month_sel).filter(month_sel.isnot(None)).distinct().subquery()
inner = (
db.session.query(month_sel)
.filter(month_sel.isnot(None))
.distinct()
.subquery()
)
else:
month_sel = month_expr(ShoppingList.created_at).label("month")
inner = db.session.query(month_sel).filter(ShoppingList.created_at.isnot(None)).distinct().subquery()
inner = (
db.session.query(month_sel)
.filter(ShoppingList.created_at.isnot(None))
.distinct()
.subquery()
)
rows = db.session.query(inner.c.month).order_by(inner.c.month).all()
return [r.month for r in rows]
@@ -1357,7 +1378,7 @@ def load_user(user_id):
@app.context_processor
def inject_version():
return {'APP_VERSION': app.config['APP_VERSION']}
return {"APP_VERSION": app.config["APP_VERSION"]}
@app.context_processor
@@ -1387,7 +1408,7 @@ def require_system_password():
"static_bp.serve_css_lib",
"favicon",
"favicon_ico",
"uploaded_file"
"uploaded_file",
):
return
@@ -1401,10 +1422,7 @@ def require_system_password():
if endpoint is None:
return
if (
"authorized" not in request.cookies
and not endpoint.startswith("login")
):
if "authorized" not in request.cookies and not endpoint.startswith("login"):
if request.path == "/":
return redirect(url_for("system_auth"))
@@ -1799,6 +1817,7 @@ def edit_my_list(list_id):
if request.method == "POST":
action = request.form.get("action")
# --- Nadanie dostępu ---
if action == "grant":
grant_username = (request.form.get("grant_username") or "").strip().lower()
if not grant_username:
@@ -1829,75 +1848,82 @@ def edit_my_list(list_id):
flash("Ten użytkownik już ma dostęp.", "info")
return redirect(next_page or request.url)
else:
revoke_user_id = request.form.get("revoke_user_id")
if revoke_user_id:
try:
uid = int(revoke_user_id)
except ValueError:
flash("Błędny identyfikator użytkownika.", "danger")
return redirect(next_page or request.url)
ListPermission.query.filter_by(list_id=l.id, user_id=uid).delete()
db.session.commit()
flash("Odebrano dostęp użytkownikowi.", "success")
# --- Odebranie dostępu ---
revoke_user_id = request.form.get("revoke_user_id")
if revoke_user_id:
try:
uid = int(revoke_user_id)
except ValueError:
flash("Błędny identyfikator użytkownika.", "danger")
return redirect(next_page or request.url)
if "unarchive" in request.form:
l.is_archived = False
db.session.commit()
flash(f"Lista „{l.title}” została przywrócona.", "success")
return redirect(next_page or request.url)
move_to_month = request.form.get("move_to_month")
if move_to_month:
try:
year, month = map(int, move_to_month.split("-"))
new_created_at = datetime(year, month, 1, tzinfo=timezone.utc)
l.created_at = new_created_at
db.session.commit()
flash(
f"Zmieniono datę utworzenia listy na {new_created_at.strftime('%Y-%m-%d')}",
"success",
)
return redirect(next_page or request.url)
except ValueError:
flash("Nieprawidłowy format miesiąca", "danger")
return redirect(next_page or request.url)
new_title = (request.form.get("title") or "").strip()
is_public = "is_public" in request.form
is_temporary = "is_temporary" in request.form
is_archived = "is_archived" in request.form
expires_date = request.form.get("expires_date")
expires_time = request.form.get("expires_time")
if not new_title:
flash("Podaj poprawny tytuł", "danger")
return redirect(next_page or request.url)
l.title = new_title
l.is_public = is_public
l.is_temporary = is_temporary
l.is_archived = is_archived
if expires_date and expires_time:
try:
combined = f"{expires_date} {expires_time}"
expires_dt = datetime.strptime(combined, "%Y-%m-%d %H:%M")
l.expires_at = expires_dt.replace(tzinfo=timezone.utc)
except ValueError:
flash("Błędna data lub godzina wygasania", "danger")
return redirect(next_page or request.url)
else:
l.expires_at = None
update_list_categories_from_form(l, request.form)
ListPermission.query.filter_by(list_id=l.id, user_id=uid).delete()
db.session.commit()
flash("Zaktualizowano dane listy", "success")
flash("Odebrano dostęp użytkownikowi.", "success")
return redirect(next_page or request.url)
# --- Przywracanie z archiwum ---
if "unarchive" in request.form:
l.is_archived = False
db.session.commit()
flash(f"Lista „{l.title}” została przywrócona.", "success")
return redirect(next_page or request.url)
# --- Główny zapis pól formularza (bez wczesnych redirectów) ---
# Przenieś do miesiąca
move_to_month = request.form.get("move_to_month")
if move_to_month:
try:
year, month = map(int, move_to_month.split("-"))
l.created_at = datetime(year, month, 1, tzinfo=timezone.utc)
flash(
f"Zmieniono datę utworzenia listy na {l.created_at.strftime('%Y-%m-%d')}",
"success",
)
except ValueError:
# Błędny format: informujemy, ale pozwalamy zapisać resztę pól
flash(
"Nieprawidłowy format miesiąca — zignorowano zmianę miesiąca.",
"danger",
)
# Tytuł i statusy
new_title = (request.form.get("title") or "").strip()
is_public = "is_public" in request.form
is_temporary = "is_temporary" in request.form
is_archived = "is_archived" in request.form
expires_date = request.form.get("expires_date")
expires_time = request.form.get("expires_time")
if not new_title:
flash("Podaj poprawny tytuł", "danger")
return redirect(next_page or request.url)
l.title = new_title
l.is_public = is_public
l.is_temporary = is_temporary
l.is_archived = is_archived
# Wygasanie
if expires_date and expires_time:
try:
combined = f"{expires_date} {expires_time}"
expires_dt = datetime.strptime(combined, "%Y-%m-%d %H:%M")
l.expires_at = expires_dt.replace(tzinfo=timezone.utc)
except ValueError:
flash("Błędna data lub godzina wygasania", "danger")
return redirect(next_page or request.url)
else:
l.expires_at = None
# Kategorie
update_list_categories_from_form(l, request.form)
# Jeden commit na koniec
db.session.commit()
flash("Zaktualizowano dane listy", "success")
return redirect(next_page or request.url)
permitted_users = (
db.session.query(User)
.join(ListPermission, ListPermission.user_id == User.id)
@@ -2051,6 +2077,10 @@ def view_list(list_id):
for c in shopping_list.categories
]
# dane do modala kategorii
categories = Category.query.order_by(Category.name.asc()).all()
selected_categories_ids = {c.id for c in shopping_list.categories}
return render_template(
"list.html",
list=shopping_list,
@@ -2063,9 +2093,83 @@ def view_list(list_id):
total_expense=total_expense,
is_share=False,
is_owner=is_owner,
categories=categories,
selected_categories=selected_categories_ids,
)
# proste akcje ustawień listy
@app.route("/list/<int:list_id>/settings", methods=["POST"])
@login_required
def list_settings(list_id):
l = db.session.get(ShoppingList, list_id)
if l is None:
abort(404)
if l.owner_id != current_user.id:
abort(403, description="Nie jesteś właścicielem tej listy.")
next_page = request.form.get("next") or url_for("view_list", list_id=list_id)
action = (request.form.get("action") or "").strip()
if action == "set_category":
cat_id = request.form.get("category_id", "").strip()
if not cat_id:
l.categories.clear()
db.session.commit()
flash("Usunięto kategorię.", "success")
return redirect(next_page)
try:
cid = int(cat_id)
except ValueError:
flash("Nieprawidłowa kategoria.", "danger")
return redirect(next_page)
cat = db.session.get(Category, cid)
if not cat:
flash("Taka kategoria nie istnieje.", "danger")
return redirect(next_page)
# pojedyncza kategoria
l.categories = [cat]
db.session.commit()
flash(f"Ustawiono kategorię: „{cat.name}”.", "success")
return redirect(next_page)
# 2) Nadanie dostępu użytkownikowi
if action == "grant_access":
grant_username = (request.form.get("grant_username") or "").strip().lower()
if not grant_username:
flash("Podaj login użytkownika.", "danger")
return redirect(next_page)
u = User.query.filter(func.lower(User.username) == grant_username).first()
if not u:
flash("Użytkownik nie istnieje.", "danger")
return redirect(next_page)
if u.id == current_user.id:
flash("Jesteś właścicielem tej listy.", "info")
return redirect(next_page)
exists = (
db.session.query(ListPermission.id)
.filter(ListPermission.list_id == l.id, ListPermission.user_id == u.id)
.first()
)
if exists:
flash("Ten użytkownik już ma dostęp.", "info")
return redirect(next_page)
db.session.add(ListPermission(list_id=l.id, user_id=u.id))
db.session.commit()
flash(f"Nadano dostęp użytkownikowi „{u.username}”.", "success")
return redirect(next_page)
# nieznana akcja
flash("Nieznana akcja.", "warning")
return redirect(next_page)
@app.route("/expenses")
@login_required
def expenses():
@@ -4071,4 +4175,4 @@ def create_db():
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG if DEBUG_MODE else logging.INFO)
socketio.run(app, host="0.0.0.0", port = APP_PORT, debug=False)
socketio.run(app, host="0.0.0.0", port=APP_PORT, debug=False)