Projekt-Sichtbarkeit: Nutzer-Zuordnung, projektbezogene Filterung und Schreibschutz
- Neues Modell ProjektUser (projekt_users) für projektbezogene Sichtbarkeit
- Migration V01_09 legt Tabelle an (keine Initial-Befüllung)
- CRUD-Logik für Projekt-User-Zuordnungen mit Cross-DB-User-Auflösung
- Serverseitige Sichtbarkeits-Filterung: Projekt-Liste/Get filtert nach
projekt_users-Zuordnung für Nicht-SysAdmin/Nicht-AppAdmin
- Auto-Assignment: Ersteller wird bei Projektanlage automatisch zugeordnet
- Sichtbarkeits-Filterung auf CallLog und Kontaktadresse erweitert
(Liste und Einzelabruf)
- Schreibschutz: Alle Schreib-Endpunkte (update/delete) für Projekt,
Kontaktadresse und CallLog prüfen Projekt-Zuordnung vor der Ausführung
- Hilfsfunktionen _get_visible_user_id() und _require_projekt_access()
zur Kapselung der wiederkehrenden Prüfungen
- REST-Endpunkte /xhr/projektuser/{list,add,remove};
Schreib-Endpunkte mit @authorisation_required([]) als Erweiterungspunkt
- Client: Neues Modell projekt_user.ts, Listen-Komponente mit Modal
(Select-Widget), Button in Projekt-Maske, Route registriert
- Build aktualisiert (tsc + rollup)
- AGENTS.md: Vorgaben für projektbezogene Sichtbarkeit dokumentiert
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
b2f8684afb
commit
4cb0084066
12 files changed
+758
-25
No files matched your search
@@ -10,31 +10,54 @@ dort über den Decorator authorisation_required.
|
||||
:version: 1.0
|
||||
"""
|
||||
from db import dbapp
|
||||
from crm.model import Projekt, Kunde
|
||||
from crm.model import Projekt, Kunde, ProjektUser
|
||||
|
||||
from auth.model.errors import ApplicationError, HtmlStatusCode
|
||||
from flask_login import current_user
|
||||
current_user: object
|
||||
|
||||
session = dbapp.session
|
||||
|
||||
|
||||
def get_projekte(kunden_id: int | None = None):
|
||||
def get_projekte(kunden_id: int | None = None, user_id: int | None = None):
|
||||
"""
|
||||
Liefert Projekte, sortiert nach projektname.
|
||||
|
||||
Ist kunden_id gesetzt, werden nur die Projekte dieses Kunden
|
||||
zurückgegeben (serverseitige Filterung). Ohne kunden_id werden
|
||||
alle Projekte geliefert.
|
||||
|
||||
Ist user_id gesetzt, werden nur Projekte geliefert, denen der
|
||||
Nutzer über die Tabelle 'projekt_users' zugeordnet ist. Dies
|
||||
steuert die Sichtbarkeit: Nicht zugeordnete Nutzer sehen diese
|
||||
Projekte nicht. SysAdmin und AppAdmin übergeben user_id=None
|
||||
und sehen alle Projekte.
|
||||
"""
|
||||
query = Projekt.query
|
||||
if kunden_id is not None:
|
||||
query = query.filter(Projekt.kunden_id == kunden_id)
|
||||
if user_id is not None:
|
||||
# Nur Projekte, denen der Nutzer zugeordnet ist. Der Subquery
|
||||
# auf projekt_users filtert die sichtbaren Projekt-IDs.
|
||||
query = query.filter(
|
||||
Projekt.id.in_(
|
||||
dbapp.session.query(ProjektUser.projekt_id).filter(
|
||||
ProjektUser.user_id == user_id
|
||||
)
|
||||
)
|
||||
)
|
||||
return query.order_by(Projekt.projektname).all()
|
||||
|
||||
|
||||
def get_projekt(projekt_id: int) -> Projekt:
|
||||
def get_projekt(projekt_id: int, user_id: int | None = None) -> Projekt:
|
||||
"""
|
||||
Liefert ein einzelnes Projekt anhand seiner ID.
|
||||
Wirft ApplicationError (404), falls das Projekt nicht existiert.
|
||||
|
||||
Ist user_id gesetzt, wird zusätzlich geprüft, ob der Nutzer dem
|
||||
Projekt zugeordnet ist. Ist er nicht zugeordnet, wird ein 404
|
||||
geliefert, damit nicht zugeordnete Nutzer keine Rückschlüsse auf
|
||||
die Existenz des Projekts ziehen können.
|
||||
"""
|
||||
projekt = Projekt.query.get(projekt_id)
|
||||
if projekt is None:
|
||||
@@ -42,6 +65,19 @@ def get_projekt(projekt_id: int) -> Projekt:
|
||||
"Projekt mit der ID {} wurde nicht gefunden.".format(projekt_id),
|
||||
htmlstatus=HtmlStatusCode.Not_Found,
|
||||
)
|
||||
if user_id is not None:
|
||||
# Sichtbarkeitsprüfung: Ist der Nutzer dem Projekt zugeordnet?
|
||||
zuordnung = ProjektUser.query.filter(
|
||||
ProjektUser.projekt_id == projekt_id,
|
||||
ProjektUser.user_id == user_id,
|
||||
).first()
|
||||
if zuordnung is None:
|
||||
# 404 statt 403, damit nicht zugeordnete Nutzer keine Info über
|
||||
# die Existenz des Projekts erhalten.
|
||||
raise ApplicationError(
|
||||
"Projekt mit der ID {} wurde nicht gefunden.".format(projekt_id),
|
||||
htmlstatus=HtmlStatusCode.Not_Found,
|
||||
)
|
||||
return projekt
|
||||
|
||||
|
||||
@@ -78,6 +114,14 @@ def create_projekt(data: dict) -> Projekt:
|
||||
projekt.startdatum = (data.get("startdatum") or "").strip() or None
|
||||
session.add(projekt)
|
||||
session.commit()
|
||||
# Auto-Assignment: Den Ersteller dem neuen Projekt zuordnen, damit
|
||||
# er es sehen kann. Nur für authentifizierte Nutzer mit einer id.
|
||||
if hasattr(current_user, "id") and current_user.id is not None:
|
||||
zuordnung = ProjektUser()
|
||||
zuordnung.projekt_id = projekt.id
|
||||
zuordnung.user_id = current_user.id
|
||||
session.add(zuordnung)
|
||||
session.commit()
|
||||
return projekt
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user