diff --git a/app.py b/app.py index 48f3a05..e3ed435 100644 --- a/app.py +++ b/app.py @@ -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//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) diff --git a/templates/list.html b/templates/list.html index 158eb53..c5b3109 100644 --- a/templates/list.html +++ b/templates/list.html @@ -12,16 +12,20 @@ {% if list.category_badges %} {% for cat in list.category_badges %} + font-size: 0.75rem; + opacity: 0.85;"> {{ cat.name }} {% endfor %} + + {% else %} - + + {% endif %} @@ -30,15 +34,13 @@ list.is_public %}disabled{% endif %}> ✅ Otwórz tryb zakupowy / odznaczania produktów +
- {% if list.is_public %} - 🔗 Udostępnij link (lista publiczna) - {% else %} - 🔗 Udostępnij link (widoczna przez link / uprawnienia) - {% endif %} + {% if list.is_public %}🔗 Udostępnij link (lista publiczna){% else %}🔗 Udostępnij link (widoczna przez link / + uprawnienia){% endif %} {{ request.url_root }}share/{{ list.share_token }} @@ -52,16 +54,13 @@ - + + +
@@ -76,14 +75,11 @@
-
+ title="Kupione produkty">
-
+ title="Oznaczone jako niekupione">
-
+ title="Pozostałe do kupienia"> @@ -98,9 +94,8 @@ {% endif %}
- +
@@ -111,14 +106,12 @@ {% for item in items %}
  • - - {{ item.name }} {% if item.quantity and item.quantity > 1 %} @@ -128,18 +121,12 @@
    {% set info_parts = [] %} - {% if item.note %} - {% set _ = info_parts.append('[ ' ~ item.note ~ ' ]') %} - {% endif %} - {% if item.not_purchased_reason %} - {% set _ = info_parts.append('[ Powód: ' ~ item.not_purchased_reason ~ ' - ]') %} - {% endif %} - {% if item.added_by_display %} - {% set _ = info_parts.append('[ Dodał/a: ' ~ item.added_by_display ~ ' ]') - %} - {% endif %} - + {% if item.note %}{% set _ = info_parts.append('[ ' ~ item.note ~ ' ]') + %}{% endif %} + {% if item.not_purchased_reason %}{% set _ = info_parts.append('[ Powód: ' ~ + item.not_purchased_reason ~ ' ]') %}{% endif %} + {% if item.added_by_display %}{% set _ = info_parts.append('[ Dodał/a: ' ~ + item.added_by_display ~ ' ]') %}{% endif %} {% if info_parts %}
    {{ info_parts | join(' ') | safe }} @@ -150,34 +137,24 @@
    {% if not is_share %} - - + + {% endif %} {% if item.not_purchased %} - + {% elif not item.not_purchased %} - + {% endif %}
  • - {% else %} -
  • - Brak produktów w tej liście. -
  • +
  • Brak produktów w tej + liście.
  • {% endfor %} @@ -216,12 +193,83 @@
    {% endfor %} {% else %} - + {% endif %}
    + + + + + + + +