30 KiB
Schritt 2: Interface erstellen
← Zurück: Datenmodell erstellen | Weiter: Feature-Logik implementieren →
Übersicht: Was sind Interfaces?
Interfaces sind aktive Klassen, die den Datenbankzugriff implementieren. Sie unterscheiden sich von Datamodels (die nur die Datenstruktur definieren):
| Aspekt | Datamodels | Interfaces |
|---|---|---|
| Zweck | Definiert WAS (Datenstruktur) | Implementiert WIE (Datenzugriff) |
| Inhalt | Pydantic-Modelle mit Feldern und Validierung | Klassen mit CRUD-Methoden (create, get, update, delete) |
| Beispiel | class Projekt(BaseModel): ... |
def createProjekt(...) -> Projekt: ... |
| Aktivität | Passiv (nur Struktur) | Aktiv (führt Operationen aus) |
Analogie:
- Datamodel = Bauplan (beschreibt das Haus)
- Interface = Bauunternehmer (baut das Haus)
Struktur: Real Estate CRUD-Interface
Da das Feature stateless arbeitet, benötigen wir nur ein Interface für CRUD-Operationen auf Real Estate-Entitäten:
Real Estate-Datenmodelle → Real Estate CRUD-Interface
Datamodel: datamodelRealEstate.py
ProjektParzelleDokumentKanton,Gemeinde,LandGeoPolylinie,GeoPunktKontext- etc.
Interface: interfaceDbRealEstateObjects.py
RealEstateObjects(Haupt-Interface)RealEstateAccess(Zugriffskontrolle)- Methoden:
createProjekt(),getParzelle(),updateDokument(), etc.
Optional: interfaceDbRealEstateChatObjects.py (nur für direkte SQL-Queries)
RealEstateChatObjects- Für direkte Query-Ausführung ohne Session- Methoden:
executeQuery()- Führt SQL direkt aus
Warum nur ein Haupt-Interface?
-
Stateless Design:
- Keine Session-Verwaltung notwendig
- Direkte CRUD-Operationen auf Real Estate-Modellen
-
Einfache Architektur:
- Ein Interface für alle CRUD-Operationen
- Weniger Komplexität, bessere Wartbarkeit
-
Optionales Query-Interface:
- Nur für direkte SQL-Queries (stateless)
- Keine Session-Management-Funktionen
Zu erstellende Dateien
Schritt 2a: Real Estate CRUD-Interface (ERFORDERLICH)
Zwei separate Dateien (wie bei anderen Features):
Datei 1: modules/interfaces/interfaceDbRealEstateAccess.py
Enthält:
RealEstateAccess- Zugriffskontrolle für Real Estate-Entitäten- Methoden:
uam(),canModify()
Zweck: Prüft Zugriffsrechte und filtert Daten basierend auf Benutzerprivilegien
Datei 2: modules/interfaces/interfaceDbRealEstateObjects.py
Enthält:
RealEstateObjects- Haupt-Interface für CRUD-OperationengetInterface()- Factory-Funktion- Nutzt
RealEstateAccessaus der Access-Datei
Zweck: Verwaltet Real Estate-Entitäten (Projekt, Parzelle, Dokument, etc.)
Nutzt:
datamodelRealEstate.py(Projekt, Parzelle, Dokument, etc.)interfaceDbRealEstateAccess.py(für Zugriffskontrolle)
Wann benötigt: Für alle CRUD-Operationen auf Real Estate-Entitäten (z.B. Projekte erstellen/bearbeiten, Parzellen verwalten). Dies ist das Haupt-Interface für das Feature.
Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
Eine Datei für stateless Query-Ausführung:
Datei: modules/interfaces/interfaceDbRealEstateChatObjects.py
Enthält:
RealEstateChatObjects- Interface für direkte SQL-Query-AusführunggetInterface()- Factory-Funktion- Methoden:
executeQuery()- Führt SQL direkt aus (stateless)
Zweck: Direkte SQL-Query-Ausführung ohne Session-Management
Nutzt:
connectorDbPostgre.DatabaseConnectorfür direkte SQL-Ausführung- Keine Chat-Modelle (stateless)
Wann benötigt: Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
Übersicht: Dateien und ihre Beziehungen
┌─────────────────────────────────────────────────────────────┐
│ DATAMODELS (Struktur) │
├─────────────────────────────────────────────────────────────┤
│ datamodelRealEstate.py │
│ ├── Projekt │
│ ├── Parzelle │
│ ├── Dokument │
│ ├── Kanton, Gemeinde, Land │
│ ├── GeoPolylinie, GeoPunkt │
│ ├── Kontext │
│ └── ... │
└─────────────────────────────────────────────────────────────┘
│
│ nutzt
▼
┌─────────────────────────────────────────────────────────────┐
│ INTERFACES (Zugriff) │
├─────────────────────────────────────────────────────────────┤
│ REAL ESTATE CRUD-INTERFACE (ERFORDERLICH) │
│ │
│ interfaceDbRealEstateAccess.py │
│ └── RealEstateAccess │
│ ├── uam() │
│ └── canModify() │
│ │
│ interfaceDbRealEstateObjects.py │
│ ├── RealEstateObjects │
│ │ ├── createProjekt() │
│ │ ├── getProjekt() │
│ │ ├── updateProjekt() │
│ │ ├── deleteProjekt() │
│ │ ├── createParzelle() │
│ │ ├── getParzelle() │
│ │ └── ... (CRUD für alle Entitäten) │
│ └── getInterface() │
│ └── nutzt RealEstateAccess │
│ │
│ QUERY-INTERFACE (OPTIONAL - nur für direkte SQL) │
│ │
│ interfaceDbRealEstateChatObjects.py │
│ ├── RealEstateChatObjects │
│ │ └── executeQuery() # Direkte SQL-Ausführung │
│ └── getInterface() │
│ └── Keine Access-Klasse (stateless) │
└─────────────────────────────────────────────────────────────┘
Interface-Struktur: Access vs. Objects
Jedes Interface besteht aus zwei Klassen:
1. *Access Klasse (Zugriffskontrolle)
Zweck: Prüft, wer was sehen/dürfen darf
Methoden:
uam()- Filtert Daten basierend auf BenutzerprivilegiencanModify()- Prüft, ob Benutzer ändern darf
Beispiel: RealEstateChatAccess
2. *Objects Klasse (Haupt-Interface)
Zweck: Führt CRUD-Operationen aus
Methoden:
create*()- Erstellt neue Einträgeget*()- Lädt Einträgeupdate*()- Aktualisiert Einträgedelete*()- Löscht Einträge
Nutzt: *Access für Zugriffskontrolle
Beispiel: RealEstateChatObjects
Warum getrennt?
- Separation of Concerns: Zugriffskontrolle ist separate Verantwortlichkeit
- Wiederverwendbarkeit: Access-Klasse kann von mehreren Interfaces genutzt werden
- Testbarkeit: Zugriffskontrolle kann unabhängig getestet werden
Implementierung: Real Estate CRUD-Interface
Das Real Estate CRUD-Interface besteht aus zwei separaten Dateien, genau wie bei anderen Features (interfaceDbAppObjects.py + interfaceDbAppAccess.py).
Datei 1: Access-Implementierung
Datei: modules/interfaces/interfaceDbRealEstateAccess.py
"""
Access control for Real Estate interface.
Handles user access management and permission checks.
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelUam import User, UserPrivilege
logger = logging.getLogger(__name__)
class RealEstateAccess:
"""
Access control class for Real Estate interface.
Handles user access management and permission checks.
"""
def __init__(self, currentUser: User, db):
"""Initialize with user context."""
self.currentUser = currentUser
self.mandateId = currentUser.mandateId
self.userId = currentUser.id
if not self.mandateId or not self.userId:
raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db
def uam(self, model_class: type, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Unified user access management function that filters data based on user privileges.
Args:
model_class: Pydantic model class for the table
recordset: Recordset to filter based on access rules
Returns:
Filtered recordset with access control attributes
"""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
filtered_records = []
# System admins see all records
if userPrivilege == UserPrivilege.SYSADMIN:
filtered_records = recordset
# Admins see records in their mandate
elif userPrivilege == UserPrivilege.ADMIN:
filtered_records = [r for r in recordset if r.get("mandateId", "-") == self.mandateId]
# Regular users see only their records
else:
filtered_records = [
r for r in recordset
if r.get("mandateId", "-") == self.mandateId and r.get("_createdBy") == self.userId
]
# Add access control attributes
for record in filtered_records:
record["_hideView"] = False
record["_hideEdit"] = not self.canModify(model_class, record.get("id"))
record["_hideDelete"] = not self.canModify(model_class, record.get("id"))
return filtered_records
def canModify(self, model_class: type, recordId: Optional[str] = None) -> bool:
"""Checks if the current user can modify records."""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
if userPrivilege == UserPrivilege.SYSADMIN:
return True
if recordId is not None:
records = self.db.getRecordset(model_class, recordFilter={"id": recordId})
if not records:
return False
record = records[0]
if userPrivilege == UserPrivilege.ADMIN and record.get("mandateId", "-") == self.mandateId:
return True
if (record.get("mandateId", "-") == self.mandateId and
record.get("_createdBy") == self.userId):
return True
return False
else:
return True # Regular users can create records
Datei 2: Objects-Implementierung
Datei: modules/interfaces/interfaceDbRealEstateObjects.py
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities (Projekt, Parzelle, etc.).
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelRealEstate import (
Projekt,
Parzelle,
Dokument,
Kanton,
Gemeinde,
Land,
GeoPolylinie,
GeoPunkt,
Kontext,
StatusProzess,
)
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
# Import Access-Klasse aus separater Datei
from modules.interfaces.interfaceDbRealEstateAccess import RealEstateAccess
logger = logging.getLogger(__name__)
# Singleton factory for Real Estate interfaces
_realEstateInterfaces = {}
class RealEstateObjects:
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities.
"""
def __init__(self, currentUser: Optional[User] = None):
"""Initializes the Real Estate Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
self.access = None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
# Get database configuration from environment
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
# Initialize database connector
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Initialize access control
self.access = RealEstateAccess(self.currentUser, self.db)
# Update database context
self.db.updateContext(self.userId)
# ===== Projekt Methods =====
def createProjekt(self, projekt: Projekt) -> Projekt:
"""Create a new project."""
# Ensure mandateId is set
if not projekt.mandateId:
projekt.mandateId = self.mandateId
# Apply access control
self.access.uam(Projekt, [])
# Save to database
self.db.recordCreate(Projekt, projekt.model_dump())
return projekt
def getProjekt(self, projektId: str) -> Optional[Projekt]:
"""Get a project by ID."""
records = self.db.getRecordset(
Projekt,
recordFilter={"id": projektId}
)
if not records:
return None
# Apply access control
filtered = self.access.uam(Projekt, records)
if not filtered:
return None
return Projekt(**filtered[0])
def updateProjekt(self, projektId: str, updateData: Dict[str, Any]) -> Optional[Projekt]:
"""Update a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return None
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot modify project {projektId}")
# Update fields
for key, value in updateData.items():
if hasattr(projekt, key):
setattr(projekt, key, value)
# Save to database
self.db.recordModify(Projekt, projektId, projekt.model_dump())
return projekt
def deleteProjekt(self, projektId: str) -> bool:
"""Delete a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return False
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot delete project {projektId}")
return self.db.recordDelete(Projekt, projektId)
# ===== Parzelle Methods =====
def createParzelle(self, parzelle: Parzelle) -> Parzelle:
"""Create a new plot."""
if not parzelle.mandateId:
parzelle.mandateId = self.mandateId
self.access.uam(Parzelle, [])
self.db.recordCreate(Parzelle, parzelle.model_dump())
return parzelle
def getParzelle(self, parzelleId: str) -> Optional[Parzelle]:
"""Get a plot by ID."""
records = self.db.getRecordset(
Parzelle,
recordFilter={"id": parzelleId}
)
if not records:
return None
filtered = self.access.uam(Parzelle, records)
if not filtered:
return None
return Parzelle(**filtered[0])
def updateParzelle(self, parzelleId: str, updateData: Dict[str, Any]) -> Optional[Parzelle]:
"""Update a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return None
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot modify plot {parzelleId}")
for key, value in updateData.items():
if hasattr(parzelle, key):
setattr(parzelle, key, value)
self.db.recordModify(Parzelle, parzelleId, parzelle.model_dump())
return parzelle
def deleteParzelle(self, parzelleId: str) -> bool:
"""Delete a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return False
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot delete plot {parzelleId}")
return self.db.recordDelete(Parzelle, parzelleId)
# ===== Dokument Methods =====
def createDokument(self, dokument: Dokument) -> Dokument:
"""Create a new document."""
if not dokument.mandateId:
dokument.mandateId = self.mandateId
self.access.uam(Dokument, [])
self.db.recordCreate(Dokument, dokument.model_dump())
return dokument
def getDokument(self, dokumentId: str) -> Optional[Dokument]:
"""Get a document by ID."""
records = self.db.getRecordset(
Dokument,
recordFilter={"id": dokumentId}
)
if not records:
return None
filtered = self.access.uam(Dokument, records)
if not filtered:
return None
return Dokument(**filtered[0])
# ... weitere CRUD-Methoden für andere Entitäten (Kanton, Gemeinde, Land, etc.)
def getInterface(currentUser: User) -> RealEstateObjects:
"""
Factory function to get or create a Real Estate interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateInterfaces:
_realEstateInterfaces[userKey] = RealEstateObjects(currentUser)
return _realEstateInterfaces[userKey]
Wichtige Punkte:
- DatabaseConnector: Nutzt
connectorDbPostgre.DatabaseConnectorfür Datenbankzugriff - Access Control:
RealEstateAccessimplementiert Benutzer- und Mandaten-Filterung - Singleton Pattern:
getInterface()erstellt pro User eine Instanz - CRUD-Operationen:
recordCreate,recordModify,recordDelete,getRecordsetvom Connector - MandateId: Wird automatisch gesetzt, wenn nicht vorhanden
Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
Wann benötigt?
Kurze Antwort: Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
Szenario 1: Alles über CRUD-Interface (EMPFOHLEN)
Strukturiert und sicher:
# User schreibt: "Erstelle Projekt 'Test'"
# Feature-Logik nutzt CRUD-Interface:
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId, # Automatisch gesetzt
label="Test" # Validierung durch Pydantic
))
Vorteile:
- ✅ Validierung: Pydantic-Modelle prüfen alle Felder automatisch
- ✅ Zugriffskontrolle:
RealEstateAccessprüft Berechtigungen - ✅ Sicherheit: Kein SQL-Injection-Risiko
- ✅ Geschäftslogik: Automatisches Setzen von Systemfeldern (
_createdBy,_createdAt) - ✅ Typsicherheit: Fehler werden zur Entwicklungszeit erkannt
- ✅ Wartbarkeit: Zentrale CRUD-Methoden, einfach zu testen
Szenario 2: Direkte SQL-Queries (OPTIONAL)
Nur für komplexe SELECT-Queries:
# Für komplexe Queries, die nicht über CRUD-Methoden abgedeckt sind
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
Warnung:
- ⚠️ Nur für SELECT-Queries: Keine INSERT/UPDATE/DELETE über direkte SQL-Queries
- ⚠️ Validierung erforderlich: Queries sollten validiert werden
- ⚠️ SQL-Injection-Risiko: Immer Parameterisierung verwenden
Empfehlung
Sie benötigen das Query-Interface, wenn Sie:
- ✅ Komplexe SELECT-Queries benötigen (z.B. JOINs, Aggregationen)
- ✅ Flexible Query-Ausführung benötigen (nicht über CRUD-Methoden abgedeckt)
Sie benötigen es NICHT, wenn Sie:
- ✅ Nur CRUD-Operationen benötigen (verwenden Sie das CRUD-Interface)
- ✅ Einfache Queries haben (können über CRUD-Methoden abgedeckt werden)
Für Production-Systeme:
- CRUD-Operationen: Immer über CRUD-Interface
- Komplexe Queries: Optional über Query-Interface (nur SELECT)
Struktur: Query-Interface (stateless)
Eine Datei für direkte SQL-Query-Ausführung:
Datei: modules/interfaces/interfaceDbRealEstateChatObjects.py
"""
Interface for direct SQL query execution (stateless).
Uses PostgreSQL connector for direct query execution without session management.
"""
import logging
from typing import Dict, Any, Optional
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Singleton factory
_realEstateChatInterfaces = {}
class RealEstateChatObjects:
"""Interface for direct SQL query execution (stateless)."""
def __init__(self, currentUser: Optional[User] = None):
"""Initialize the Query Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate Query database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate Query database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Update database context
self.db.updateContext(self.userId)
# ===== Database Query Execution =====
def executeQuery(self, queryText: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
Execute a SQL query directly on the database (stateless).
WARNING: This method executes raw SQL. Ensure proper validation and sanitization
before calling this method. Consider implementing query whitelisting or
only allowing SELECT statements for production use.
Args:
queryText: SQL query string (preferably SELECT only)
parameters: Optional parameters for parameterized queries
Returns:
Dictionary with 'rows' (list of dicts), 'columns' (list of column names),
'rowCount' (int), and 'executionTime' (float)
"""
import time
try:
start_time = time.time()
# Ensure connection is alive
self.db._ensure_connection()
with self.db.connection.cursor() as cursor:
# Execute query
if parameters:
# Use parameterized query for safety
cursor.execute(queryText, parameters)
else:
cursor.execute(queryText)
# Fetch results
rows = cursor.fetchall()
# Convert to list of dictionaries
result_rows = [dict(row) for row in rows]
# Get column names
columns = [desc[0] for desc in cursor.description] if cursor.description else []
execution_time = time.time() - start_time
return {
"rows": result_rows,
"columns": columns,
"rowCount": len(result_rows),
"executionTime": execution_time,
}
except Exception as e:
logger.error(f"Error executing query: {e}")
raise
def getInterface(currentUser: User) -> RealEstateChatObjects:
"""
Factory function to get or create a Query interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateChatInterfaces:
_realEstateChatInterfaces[userKey] = RealEstateChatObjects(currentUser)
return _realEstateChatInterfaces[userKey]
Hinweise zur Implementierung
- Stateless: Keine Session-Management-Funktionen
- Nur für Queries: Primär für SELECT-Queries gedacht
- Sicherheit: Immer Parameterisierung verwenden
- Validierung: Queries sollten validiert werden (z.B. nur SELECT erlauben)
Beispiel: Beide Interfaces zusammen nutzen
from modules.interfaces.interfaceDbRealEstateChatObjects import getInterface as getChatInterface
from modules.interfaces.interfaceDbRealEstateObjects import getInterface as getRealEstateInterface
# CRUD-Interface für strukturierte Operationen
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId,
label="Neues Projekt"
))
# Query-Interface für komplexe SELECT-Queries (optional)
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
Zusammenfassung: Benötigte Dateien
Erforderlich (für CRUD-Operationen):
-
✅
modules/datamodels/datamodelRealEstate.py- Real Estate-Datenmodelle (Projekt, Parzelle, Dokument, etc.)
-
✅
modules/interfaces/interfaceDbRealEstateAccess.py- Zugriffskontrolle für Real Estate-Entitäten (RealEstateAccess)
-
✅
modules/interfaces/interfaceDbRealEstateObjects.py- Real Estate CRUD-Interface (RealEstateObjects)
- Nutzt
interfaceDbRealEstateAccess.py - Haupt-Interface für alle CRUD-Operationen
Optional (für direkte SQL-Queries):
- ⚠️
modules/interfaces/interfaceDbRealEstateChatObjects.py- Query-Interface für direkte SQL-Ausführung (RealEstateChatObjects)
- Stateless, keine Session-Management
- Nur wenn Sie komplexe SELECT-Queries benötigen
← Zurück: Datenmodell erstellen | Weiter: Feature-Logik implementieren →