rozszerzone uprawnienia
This commit is contained in:
261
app.py
261
app.py
@@ -1796,39 +1796,49 @@ def system_auth():
|
||||
@app.route("/edit_my_list/<int:list_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def edit_my_list(list_id):
|
||||
# --- Pobranie listy i weryfikacja właściciela ---
|
||||
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.")
|
||||
|
||||
# Dane do widoku
|
||||
receipts = (
|
||||
Receipt.query.filter_by(list_id=list_id)
|
||||
.order_by(Receipt.uploaded_at.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
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.")
|
||||
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
selected_categories_ids = {c.id for c in l.categories}
|
||||
|
||||
next_page = request.args.get("next") or request.referrer
|
||||
wants_json = (
|
||||
"application/json" in (request.headers.get("Accept") or "")
|
||||
or request.headers.get("X-Requested-With") == "fetch"
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action")
|
||||
|
||||
# --- Nadanie dostępu ---
|
||||
# --- Nadanie dostępu (grant) ---
|
||||
if action == "grant":
|
||||
grant_username = (request.form.get("grant_username") or "").strip().lower()
|
||||
if not grant_username:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="empty"), 400
|
||||
flash("Podaj nazwę użytkownika do nadania dostępu.", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
u = User.query.filter(func.lower(User.username) == grant_username).first()
|
||||
if not u:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="not_found"), 404
|
||||
flash("Użytkownik nie istnieje.", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
if u.id == current_user.id:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="owner"), 409
|
||||
flash("Jesteś właścicielem tej listy.", "info")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
@@ -1843,22 +1853,30 @@ def edit_my_list(list_id):
|
||||
if not exists:
|
||||
db.session.add(ListPermission(list_id=l.id, user_id=u.id))
|
||||
db.session.commit()
|
||||
if wants_json:
|
||||
return jsonify(ok=True, user={"id": u.id, "username": u.username})
|
||||
flash(f"Nadano dostęp użytkownikowi „{u.username}”.", "success")
|
||||
else:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="exists"), 409
|
||||
flash("Ten użytkownik już ma dostęp.", "info")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
# --- Odebranie dostępu ---
|
||||
# --- Odebranie dostępu (revoke) ---
|
||||
revoke_user_id = request.form.get("revoke_user_id")
|
||||
if revoke_user_id:
|
||||
try:
|
||||
uid = int(revoke_user_id)
|
||||
except ValueError:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="bad_id"), 400
|
||||
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()
|
||||
if wants_json:
|
||||
return jsonify(ok=True, removed_user_id=uid)
|
||||
flash("Odebrano dostęp użytkownikowi.", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
@@ -1866,28 +1884,29 @@ def edit_my_list(list_id):
|
||||
if "unarchive" in request.form:
|
||||
l.is_archived = False
|
||||
db.session.commit()
|
||||
if wants_json:
|
||||
return jsonify(ok=True, unarchived=True)
|
||||
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
|
||||
# --- Główny zapis pól formularza ---
|
||||
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",
|
||||
)
|
||||
if not wants_json:
|
||||
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",
|
||||
)
|
||||
if not wants_json:
|
||||
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
|
||||
@@ -1896,6 +1915,8 @@ def edit_my_list(list_id):
|
||||
expires_time = request.form.get("expires_time")
|
||||
|
||||
if not new_title:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="title_empty"), 400
|
||||
flash("Podaj poprawny tytuł", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
@@ -1904,26 +1925,29 @@ def edit_my_list(list_id):
|
||||
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:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="bad_expiry"), 400
|
||||
flash("Błędna data lub godzina wygasania", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
else:
|
||||
l.expires_at = None
|
||||
|
||||
# Kategorie
|
||||
# Kategorie (używa Twojej pomocniczej funkcji)
|
||||
update_list_categories_from_form(l, request.form)
|
||||
|
||||
# Jeden commit na koniec
|
||||
db.session.commit()
|
||||
if wants_json:
|
||||
return jsonify(ok=True, saved=True)
|
||||
flash("Zaktualizowano dane listy", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
# GET: użytkownicy z dostępem
|
||||
permitted_users = (
|
||||
db.session.query(User)
|
||||
.join(ListPermission, ListPermission.user_id == User.id)
|
||||
@@ -1942,6 +1966,52 @@ def edit_my_list(list_id):
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.route("/edit_my_list/<int:list_id>/suggestions", methods=["GET"])
|
||||
@login_required
|
||||
def edit_my_list_suggestions(list_id: int):
|
||||
# Weryfikacja listy i właściciela (prywatność)
|
||||
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.")
|
||||
|
||||
q = (request.args.get("q") or "").strip().lower()
|
||||
|
||||
# Historia nadawań uprawnień przez tego właściciela (po wszystkich jego listach)
|
||||
subq = (
|
||||
db.session.query(
|
||||
ListPermission.user_id.label("uid"),
|
||||
func.count(ListPermission.id).label("grant_count"),
|
||||
func.max(ListPermission.id).label("last_grant_id"),
|
||||
)
|
||||
.join(ShoppingList, ShoppingList.id == ListPermission.list_id)
|
||||
.filter(ShoppingList.owner_id == current_user.id)
|
||||
.group_by(ListPermission.user_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
query = (
|
||||
db.session.query(User.username, subq.c.grant_count, subq.c.last_grant_id)
|
||||
.join(subq, subq.c.uid == User.id)
|
||||
)
|
||||
if q:
|
||||
query = query.filter(func.lower(User.username).like(f"{q}%"))
|
||||
|
||||
rows = (
|
||||
query.order_by(
|
||||
subq.c.grant_count.desc(),
|
||||
subq.c.last_grant_id.desc(),
|
||||
func.lower(User.username).asc(),
|
||||
)
|
||||
.limit(20)
|
||||
.all()
|
||||
)
|
||||
|
||||
return jsonify({"users": [r.username for r in rows]})
|
||||
|
||||
|
||||
@app.route("/delete_user_list/<int:list_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_user_list(list_id):
|
||||
@@ -2040,47 +2110,54 @@ def create_list():
|
||||
|
||||
@app.route("/list/<int:list_id>")
|
||||
@login_required
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Widok listy właściciela – dopięcie permitted_users do kontekstu
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@login_required
|
||||
def view_list(list_id):
|
||||
|
||||
shopping_list = db.session.get(ShoppingList, list_id)
|
||||
if not shopping_list:
|
||||
abort(404)
|
||||
|
||||
is_owner = current_user.id == shopping_list.owner_id
|
||||
if not is_owner:
|
||||
flash(
|
||||
"Nie jesteś właścicielem listy, przekierowano do widoku publicznego.",
|
||||
"warning",
|
||||
)
|
||||
flash("Nie jesteś właścicielem listy, przekierowano do widoku publicznego.", "warning")
|
||||
if current_user.is_admin:
|
||||
flash(
|
||||
"W celu modyfikacji listy, przejdź do panelu administracyjnego.", "info"
|
||||
)
|
||||
flash("W celu modyfikacji listy, przejdź do panelu administracyjnego.", "info")
|
||||
return redirect(url_for("shared_list", token=shopping_list.share_token))
|
||||
|
||||
# Twoja obecna logika ładująca szczegóły listy:
|
||||
shopping_list, items, receipts, expenses, total_expense = get_list_details(list_id)
|
||||
total_count = len(items)
|
||||
purchased_count = len([i for i in items if i.purchased])
|
||||
percent = (purchased_count / total_count * 100) if total_count > 0 else 0
|
||||
is_owner = current_user.id == shopping_list.owner_id
|
||||
|
||||
# Uzupełnienie "added_by_display" — jak u Ciebie:
|
||||
for item in items:
|
||||
if item.added_by != shopping_list.owner_id:
|
||||
item.added_by_display = (
|
||||
item.added_by_user.username if item.added_by_user else "?"
|
||||
)
|
||||
item.added_by_display = (item.added_by_user.username if item.added_by_user else "?")
|
||||
else:
|
||||
item.added_by_display = None
|
||||
|
||||
# Badges kategorii (jak u Ciebie)
|
||||
shopping_list.category_badges = [
|
||||
{"name": c.name, "color": category_to_color(c.name)}
|
||||
for c in shopping_list.categories
|
||||
]
|
||||
|
||||
# dane do modala kategorii
|
||||
# Dane do modala kategorii
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
selected_categories_ids = {c.id for c in shopping_list.categories}
|
||||
|
||||
# ⬅️ NOWE: użytkownicy z uprawnieniami do tej listy (dla modala w list.html)
|
||||
permitted_users = (
|
||||
db.session.query(User)
|
||||
.join(ListPermission, ListPermission.user_id == User.id)
|
||||
.filter(ListPermission.list_id == shopping_list.id)
|
||||
.order_by(User.username.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"list.html",
|
||||
list=shopping_list,
|
||||
@@ -2095,78 +2172,140 @@ def view_list(list_id):
|
||||
is_owner=is_owner,
|
||||
categories=categories,
|
||||
selected_categories=selected_categories_ids,
|
||||
permitted_users=permitted_users, # ⬅️ ważne dla tokenów w modalu
|
||||
)
|
||||
|
||||
|
||||
# proste akcje ustawień listy
|
||||
@app.route("/list/<int:list_id>/settings", methods=["POST"])
|
||||
@login_required
|
||||
def list_settings(list_id):
|
||||
# Uprawnienia: właściciel
|
||||
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.")
|
||||
abort(403, description="Brak uprawnień do ustawień tej listy.")
|
||||
|
||||
next_page = request.form.get("next") or url_for("view_list", list_id=list_id)
|
||||
action = (request.form.get("action") or "").strip()
|
||||
wants_json = (
|
||||
"application/json" in (request.headers.get("Accept") or "")
|
||||
or request.headers.get("X-Requested-With") == "fetch"
|
||||
)
|
||||
|
||||
action = request.form.get("action")
|
||||
|
||||
# 1) Ustawienie kategorii (pojedynczy wybór z list.html -> modal kategorii)
|
||||
if action == "set_category":
|
||||
cat_id = request.form.get("category_id", "").strip()
|
||||
if not cat_id:
|
||||
l.categories.clear()
|
||||
cid = request.form.get("category_id")
|
||||
if cid in (None, "", "none"):
|
||||
# usunięcie kategorii lub brak zmiany – w zależności od Twojej logiki
|
||||
l.categories = []
|
||||
db.session.commit()
|
||||
flash("Usunięto kategorię.", "success")
|
||||
if wants_json:
|
||||
return jsonify(ok=True, saved=True)
|
||||
flash("Zapisano kategorię.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
try:
|
||||
cid = int(cat_id)
|
||||
except ValueError:
|
||||
flash("Nieprawidłowa kategoria.", "danger")
|
||||
cid = int(cid)
|
||||
except (TypeError, ValueError):
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="bad_category"), 400
|
||||
flash("Błędna kategoria.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
cat = db.session.get(Category, cid)
|
||||
if not cat:
|
||||
flash("Taka kategoria nie istnieje.", "danger")
|
||||
c = db.session.get(Category, cid)
|
||||
if not c:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="bad_category"), 400
|
||||
flash("Błędna kategoria.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
# pojedyncza kategoria
|
||||
l.categories = [cat]
|
||||
# Jeśli jeden wybór – zastąp listę kategorii jedną:
|
||||
l.categories = [c]
|
||||
db.session.commit()
|
||||
flash(f"Ustawiono kategorię: „{cat.name}”.", "success")
|
||||
if wants_json:
|
||||
return jsonify(ok=True, saved=True)
|
||||
flash("Zapisano kategorię.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
# 2) Nadanie dostępu użytkownikowi
|
||||
if action == "grant_access":
|
||||
# 2) Nadanie dostępu (akceptuj 'grant_access' i 'grant')
|
||||
if action in ("grant_access", "grant"):
|
||||
grant_username = (request.form.get("grant_username") or "").strip().lower()
|
||||
|
||||
if not grant_username:
|
||||
flash("Podaj login użytkownika.", "danger")
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="empty_username"), 400
|
||||
flash("Podaj nazwę użytkownika.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
# Szukamy użytkownika po username (case-insensitive)
|
||||
u = User.query.filter(func.lower(User.username) == grant_username).first()
|
||||
if not u:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="not_found"), 404
|
||||
flash("Użytkownik nie istnieje.", "danger")
|
||||
return redirect(next_page)
|
||||
if u.id == current_user.id:
|
||||
|
||||
# Właściciel już ma dostęp
|
||||
if u.id == l.owner_id:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="owner"), 409
|
||||
flash("Jesteś właścicielem tej listy.", "info")
|
||||
return redirect(next_page)
|
||||
|
||||
# Czy już ma dostęp?
|
||||
exists = (
|
||||
db.session.query(ListPermission.id)
|
||||
.filter(ListPermission.list_id == l.id, ListPermission.user_id == u.id)
|
||||
.first()
|
||||
)
|
||||
if exists:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="exists"), 409
|
||||
flash("Ten użytkownik już ma dostęp.", "info")
|
||||
return redirect(next_page)
|
||||
|
||||
# Zapis uprawnienia
|
||||
db.session.add(ListPermission(list_id=l.id, user_id=u.id))
|
||||
db.session.commit()
|
||||
|
||||
if wants_json:
|
||||
# Zwracamy usera, żeby JS mógł dokleić token bez odświeżania
|
||||
return jsonify(ok=True, user={"id": u.id, "username": u.username})
|
||||
flash(f"Nadano dostęp użytkownikowi „{u.username}”.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
# nieznana akcja
|
||||
flash("Nieznana akcja.", "warning")
|
||||
# 3) Odebranie dostępu (po polu revoke_user_id, nie po action)
|
||||
revoke_uid = request.form.get("revoke_user_id")
|
||||
if revoke_uid:
|
||||
try:
|
||||
uid = int(revoke_uid)
|
||||
except (TypeError, ValueError):
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="bad_user_id"), 400
|
||||
flash("Błędny identyfikator użytkownika.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
# Nie pozwalaj usunąć właściciela
|
||||
if uid == l.owner_id:
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="cannot_revoke_owner"), 400
|
||||
flash("Nie można odebrać dostępu właścicielowi.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
ListPermission.query.filter_by(list_id=l.id, user_id=uid).delete()
|
||||
db.session.commit()
|
||||
|
||||
if wants_json:
|
||||
return jsonify(ok=True, removed_user_id=uid)
|
||||
flash("Odebrano dostęp użytkownikowi.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
# 4) Nieznana akcja
|
||||
if wants_json:
|
||||
return jsonify(ok=False, error="unknown_action"), 400
|
||||
flash("Nieznana akcja.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
|
||||
@@ -3789,7 +3928,7 @@ def admin_lists_access(list_id=None):
|
||||
query_string = f"per_page={per_page}"
|
||||
|
||||
return render_template(
|
||||
"admin/admin_lists_access.html",
|
||||
"admin/lists_access.html",
|
||||
lists=lists,
|
||||
permitted_by_list=permitted_by_list,
|
||||
page=page,
|
||||
|
Reference in New Issue
Block a user