709 lines
22 KiB
Python
709 lines
22 KiB
Python
"""
|
|
Real Estate data models for Architektur-Planungs-App.
|
|
Implements a general Swiss architecture planning data model.
|
|
(PEK is one example implementation, but the model is general-purpose)
|
|
"""
|
|
|
|
from typing import List, Dict, Any, Optional, ForwardRef
|
|
from enum import Enum
|
|
from pydantic import BaseModel, Field
|
|
from modules.datamodels.datamodelBase import PowerOnModel
|
|
from modules.shared.i18nRegistry import i18nModel
|
|
from modules.shared.timeUtils import getUtcTimestamp
|
|
import uuid
|
|
|
|
# ===== Enums =====
|
|
|
|
class StatusProzess(str, Enum):
|
|
"""Project process status"""
|
|
EINGANG = "Eingang"
|
|
ANALYSE = "Analyse"
|
|
STUDIE = "Studie"
|
|
PLANUNG = "Planung"
|
|
BAURECHTSVERFAHREN = "Baurechtsverfahren"
|
|
UMSETZUNG = "Umsetzung"
|
|
ARCHIV = "Archiv"
|
|
|
|
|
|
class DokumentTyp(str, Enum):
|
|
"""Document type for categorization"""
|
|
KANTON_BAUREGLEMENT_AKTUELL = "kantonBaureglementAktuell"
|
|
KANTON_BAUREGLEMENT_REVISION = "kantonBaureglementRevision"
|
|
KANTON_BAUVERORDNUNG_AKTUELL = "kantonBauverordnungAktuell"
|
|
KANTON_BAUVERORDNUNG_REVISION = "kantonBauverordnungRevision"
|
|
GEMEINDE_BZO_AKTUELL = "gemeindeBzoAktuell"
|
|
GEMEINDE_BZO_REVISION = "gemeindeBzoRevision"
|
|
|
|
|
|
class JaNein(str, Enum):
|
|
"""Three-valued state for optional yes/no questions"""
|
|
UNBEKANNT = "" # Empty string for unknown/not captured
|
|
JA = "Ja"
|
|
NEIN = "Nein"
|
|
|
|
|
|
class GeoTag(str, Enum):
|
|
"""Geopoint categories"""
|
|
K1 = "K1" # Fixpunkt höchster Genauigkeit
|
|
K2 = "K2" # Fixpunkt mittlerer Genauigkeit
|
|
K3 = "K3" # Fixpunkt niedriger Genauigkeit
|
|
GEOMETER = "Geometer" # Vom Geometer vermessener Punkt
|
|
|
|
|
|
# ===== Helper Models (must be defined before main models) =====
|
|
|
|
class GeoPunkt(BaseModel):
|
|
"""Represents a 3D point with reference."""
|
|
koordinatensystem: str = Field(
|
|
description="Coordinate system (e.g. 'LV95', 'EPSG:2056')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
x: float = Field(
|
|
description="East value (E) [m], typically 2'480'000 - 2'840'000",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
y: float = Field(
|
|
description="North value (N) [m], typically 1'070'000 - 1'300'000",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
z: Optional[float] = Field(
|
|
None,
|
|
description="Height above sea level [m]",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
referenz: Optional[GeoTag] = Field(
|
|
None,
|
|
description="Point categorization",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
class GeoPolylinie(BaseModel):
|
|
"""Represents a line or polygon from multiple GeoPunkte."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
)
|
|
closed: bool = Field(
|
|
description="Is the GeoPolylinie closed (polygon)?",
|
|
frontend_type="boolean",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
punkte: List[GeoPunkt] = Field(
|
|
default_factory=list,
|
|
description="List of GeoPunkte forming the GeoPolylinie",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
|
|
|
|
@i18nModel("Dokument")
|
|
class Dokument(BaseModel):
|
|
"""Supporting data object for file and URL management with versioning."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
label="ID",
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate this document belongs to",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
label="Mandats-ID",
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance this document belongs to",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
label="Feature-Instanz-ID",
|
|
)
|
|
label: str = Field(
|
|
description="Document label",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
label="Bezeichnung",
|
|
)
|
|
versionsbezeichnung: Optional[str] = Field(
|
|
None,
|
|
description="Version number or designation (e.g. 'v1.0', 'Rev. A')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumentTyp: Optional[DokumentTyp] = Field(
|
|
None,
|
|
description="Document type",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumentReferenz: str = Field(
|
|
description="File path or URL",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
quelle: Optional[str] = Field(
|
|
None,
|
|
description="Source of the document",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
mimeType: Optional[str] = Field(
|
|
None,
|
|
description="MIME type of the document (e.g. 'application/pdf', 'image/png')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kategorienTags: List[str] = Field(
|
|
default_factory=list,
|
|
description="Document categorization tags",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
class Kontext(PowerOnModel):
|
|
"""Supporting data object for flexible additional information."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
)
|
|
thema: str = Field(
|
|
description="Theme designation",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
inhalt: str = Field(
|
|
description="Detailed information (text)",
|
|
frontend_type="textarea",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
|
|
|
|
class Land(BaseModel):
|
|
"""National level administrative entity."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
label: str = Field(
|
|
description="Country name (e.g. 'Schweiz')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
abk: Optional[str] = Field(
|
|
None,
|
|
description="Abbreviation (e.g. 'CH')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumente: List[Dokument] = Field(
|
|
default_factory=list,
|
|
description="National laws/documents",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kontextInformationen: List[Kontext] = Field(
|
|
default_factory=list,
|
|
description="National context information",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
class Kanton(PowerOnModel):
|
|
"""Cantonal level administrative entity."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
label: str = Field(
|
|
description="Canton name (e.g. 'Zürich')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
id_land: Optional[str] = Field(
|
|
None,
|
|
description="Land ID (Foreign Key) - eindeutiger Link zum Land, in welchem Land der Kanton liegt",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": False,
|
|
"frontend_required": False,
|
|
"fk_target": {"db": "poweron_realestate", "table": "Land", "labelField": "label"},
|
|
},
|
|
)
|
|
abk: Optional[str] = Field(
|
|
None,
|
|
description="Abbreviation (e.g. 'ZH')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumente: List[Dokument] = Field(
|
|
default_factory=list,
|
|
description="Cantonal documents",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kontextInformationen: List[Kontext] = Field(
|
|
default_factory=list,
|
|
description="Canton-specific context information",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
class Gemeinde(BaseModel):
|
|
"""Municipal level administrative entity."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
)
|
|
label: str = Field(
|
|
description="Municipality name (e.g. 'Zürich')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
)
|
|
id_kanton: Optional[str] = Field(
|
|
None,
|
|
description="Kanton ID (Foreign Key) - eindeutiger Link zum Kanton, in welchem Kanton die Gemeinde liegt",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": False,
|
|
"frontend_required": False,
|
|
"fk_target": {"db": "poweron_realestate", "table": "Kanton", "labelField": "label"},
|
|
},
|
|
)
|
|
plz: Optional[str] = Field(
|
|
None,
|
|
description="Postal code (for municipalities with multiple PLZ, this can be a main PLZ). Bei Gemeinden mit mehreren Postleitzahlen wird die konkrete PLZ der Parzelle im Attribut `plz` der Parzelle erfasst.",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumente: List[Dokument] = Field(
|
|
default_factory=list,
|
|
description="Municipal documents",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kontextInformationen: List[Kontext] = Field(
|
|
default_factory=list,
|
|
description="Municipality-specific context information",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
# ===== Main Models (use ForwardRef for circular references) =====
|
|
|
|
# Forward references for circular dependencies
|
|
ParzelleRef = ForwardRef('Parzelle')
|
|
|
|
|
|
@i18nModel("Parzelle")
|
|
class Parzelle(PowerOnModel):
|
|
"""Represents a plot with all building law properties."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
label="ID",
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"label": "Mandats-ID",
|
|
"fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"},
|
|
},
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"label": "Feature-Instanz-ID",
|
|
"fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"},
|
|
},
|
|
)
|
|
|
|
# Grunddaten
|
|
label: str = Field(
|
|
description="Plot designation",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
label="Bezeichnung",
|
|
)
|
|
parzellenAliasTags: List[str] = Field(
|
|
default_factory=list,
|
|
description="Additional plot names or field names",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
eigentuemerschaft: Optional[str] = Field(
|
|
None,
|
|
description="Owner of the plot",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
strasseNr: Optional[str] = Field(
|
|
None,
|
|
description="Street and house number",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
plz: Optional[str] = Field(
|
|
None,
|
|
description="Postal code of the plot",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
# Geografischer Kontext
|
|
perimeter: Optional[GeoPolylinie] = Field(
|
|
None,
|
|
description="Plot boundary as closed GeoPolylinie",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
baulinie: Optional[GeoPolylinie] = Field(
|
|
None,
|
|
description="Building line of the plot",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
kontextGemeinde: Optional[str] = Field(
|
|
None,
|
|
description="Municipality ID (Foreign Key)",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": False,
|
|
"frontend_required": False,
|
|
"fk_target": {"db": "poweron_realestate", "table": "Gemeinde", "labelField": "label"},
|
|
},
|
|
)
|
|
|
|
# Bebauungsparameter
|
|
bauzone: Optional[str] = Field(
|
|
None,
|
|
description="Building zone designation (e.g. W3, WG2, etc.)",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
az: Optional[float] = Field(
|
|
None,
|
|
description="Ausnützungsziffer",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
bz: Optional[float] = Field(
|
|
None,
|
|
description="Bebauungsziffer",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
vollgeschossZahl: Optional[int] = Field(
|
|
None,
|
|
description="Number of allowed full floors",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
anrechenbarDachgeschoss: Optional[float] = Field(
|
|
None,
|
|
description="Accountable portion of attic (0.0 - 1.0)",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
anrechenbarUntergeschoss: Optional[float] = Field(
|
|
None,
|
|
description="Accountable portion of basement (0.0 - 1.0)",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
gebaeudehoeheMax: Optional[float] = Field(
|
|
None,
|
|
description="Maximum building height in meters",
|
|
frontend_type="number",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
# Abstandsregelungen
|
|
regelnGrenzabstand: List[str] = Field(
|
|
default_factory=list,
|
|
description="Regulations for boundary distance",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
regelnMehrlaengenzuschlag: List[str] = Field(
|
|
default_factory=list,
|
|
description="Regulations for additional length surcharge",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
regelnMehrhoehenzuschlag: List[str] = Field(
|
|
default_factory=list,
|
|
description="Regulations for additional height surcharge",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
# Eigenschaften (Ja/Nein)
|
|
parzelleBebaut: Optional[JaNein] = Field(
|
|
None,
|
|
description="Is the plot built?",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
parzelleErschlossen: Optional[JaNein] = Field(
|
|
None,
|
|
description="Is the plot developed?",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
parzelleHanglage: Optional[JaNein] = Field(
|
|
None,
|
|
description="Is the plot on a slope?",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
# Schutzzonen
|
|
laermschutzzone: Optional[str] = Field(
|
|
None,
|
|
description="Noise protection zone (e.g. 'II')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
hochwasserschutzzone: Optional[str] = Field(
|
|
None,
|
|
description="Flood protection zone (e.g. 'tief')",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
grundwasserschutzzone: Optional[str] = Field(
|
|
None,
|
|
description="Groundwater protection zone",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
# Beziehungen (stored as JSONB in database)
|
|
parzellenNachbarschaft: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Neighboring plots (stored as list of Parzelle IDs or full objects)",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumente: List[Dokument] = Field(
|
|
default_factory=list,
|
|
description="Plot-specific documents",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kontextInformationen: List[Kontext] = Field(
|
|
default_factory=list,
|
|
description="Plot-specific context information",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
@i18nModel("Projekt")
|
|
class Projekt(PowerOnModel):
|
|
"""Core object representing a construction project."""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
frontend_type="text",
|
|
frontend_readonly=True,
|
|
frontend_required=False,
|
|
label="ID",
|
|
)
|
|
mandateId: str = Field(
|
|
description="ID of the mandate",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"label": "Mandats-ID",
|
|
"fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"},
|
|
},
|
|
)
|
|
featureInstanceId: str = Field(
|
|
description="ID of the feature instance",
|
|
json_schema_extra={
|
|
"frontend_type": "text",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"label": "Feature-Instanz-ID",
|
|
"fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"},
|
|
},
|
|
)
|
|
label: str = Field(
|
|
description="Project designation",
|
|
frontend_type="text",
|
|
frontend_readonly=False,
|
|
frontend_required=True,
|
|
label="Bezeichnung",
|
|
)
|
|
statusProzess: Optional[StatusProzess] = Field(
|
|
None,
|
|
description="Project status",
|
|
frontend_type="select",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
label="Prozessstatus",
|
|
)
|
|
perimeter: Optional[GeoPolylinie] = Field(
|
|
None,
|
|
description="Envelope of all plots in the project",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
baulinie: Optional[GeoPolylinie] = Field(
|
|
None,
|
|
description="Building line of the project",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
parzellen: List[Parzelle] = Field(
|
|
default_factory=list,
|
|
description="All plots of the project",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
dokumente: List[Dokument] = Field(
|
|
default_factory=list,
|
|
description="Project-specific documents",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
kontextInformationen: List[Kontext] = Field(
|
|
default_factory=list,
|
|
description="Project-specific context information",
|
|
frontend_type="json",
|
|
frontend_readonly=False,
|
|
frontend_required=False,
|
|
)
|
|
|
|
|
|
# Resolve forward references
|
|
Parzelle.model_rebuild()
|
|
Projekt.model_rebuild()
|
|
|