Remove custom sqlalchemy metadata, manage with __table_args__
Allow sqlmodel queries, with relations Remode join_with mechanisms coming from gino Handlew ith_only_columns in get_df and get_gdf Implement feature-info
This commit is contained in:
parent
1e3678fb69
commit
ec71b6ed15
18 changed files with 353 additions and 141 deletions
123
src/gisaf/api.py
123
src/gisaf/api.py
|
@ -22,7 +22,12 @@ from gisaf.security import (
|
||||||
Token,
|
Token,
|
||||||
authenticate_user, get_current_user, create_access_token,
|
authenticate_user, get_current_user, create_access_token,
|
||||||
)
|
)
|
||||||
from gisaf.registry import registry
|
from gisaf.registry import registry, NotInRegistry
|
||||||
|
from gisaf.redis_tools import store as redis_store
|
||||||
|
from gisaf.custom_store_base import BaseStore
|
||||||
|
from gisaf.models.to_migrate import (
|
||||||
|
FeatureInfo, InfoItem, Attachment, InfoCategory
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -43,7 +48,6 @@ async def bootstrap(
|
||||||
|
|
||||||
@api.post("/token")
|
@api.post("/token")
|
||||||
async def login_for_access_token(
|
async def login_for_access_token(
|
||||||
db_session: db_session,
|
|
||||||
form_data: OAuth2PasswordRequestForm = Depends()
|
form_data: OAuth2PasswordRequestForm = Depends()
|
||||||
) -> Token:
|
) -> Token:
|
||||||
user = await authenticate_user(form_data.username, form_data.password)
|
user = await authenticate_user(form_data.username, form_data.password)
|
||||||
|
@ -117,6 +121,121 @@ async def get_projects(
|
||||||
df = await db_session.run_sync(pandas_query, query)
|
df = await db_session.run_sync(pandas_query, query)
|
||||||
return df.to_dict(orient="records")
|
return df.to_dict(orient="records")
|
||||||
|
|
||||||
|
@api.get("/feature-info/{store}/{id}")
|
||||||
|
async def get_feature_info(
|
||||||
|
store: str, id: str,
|
||||||
|
) -> FeatureInfo:
|
||||||
|
store_record = registry.stores.loc[store]
|
||||||
|
model = store_record.model
|
||||||
|
|
||||||
|
if store_record.is_live:
|
||||||
|
item = await redis_store.get_feature_info(store, id)
|
||||||
|
geom = item.geometry
|
||||||
|
## Reproject to default coordinate system (WGS84),
|
||||||
|
## XXX: only for shapely geometries
|
||||||
|
if not isinstance(geom, pygeos.Geometry):
|
||||||
|
geom_reprojected = transform(reproject_func, geom)
|
||||||
|
geoInfoItems = OrderedDict()
|
||||||
|
if isinstance(geom, Point):
|
||||||
|
geoInfoItems['longitude'] = f'{geom.x:.6f}'
|
||||||
|
geoInfoItems['latitude'] = f'{geom.y:.6f}'
|
||||||
|
if geom.has_z:
|
||||||
|
geoInfoItems['elevation (m)'] = f'{geom.z:.6f}'
|
||||||
|
elif isinstance(geom, (LineString, MultiLineString)):
|
||||||
|
bounds = geom.bounds
|
||||||
|
geoInfoItems['longitude'] = f'{bounds[0]:.6f} - {bounds[2]:.6f}'
|
||||||
|
geoInfoItems['latitude'] = f'{bounds[1]:.6f} - {bounds[3]:.6f}'
|
||||||
|
geoInfoItems['length (m)'] = f'{geom_reprojected.length:.2f}'
|
||||||
|
## TODO: elevation for MultiLineString
|
||||||
|
if geom.has_z and not isinstance(geom, MultiLineString):
|
||||||
|
elevations = [cc[2] for cc in geom.coords]
|
||||||
|
elev_min = min(elevations)
|
||||||
|
elev_max = max(elevations)
|
||||||
|
if elev_min == elev_max:
|
||||||
|
geoInfoItems['elevation (m)'] = f'{elev_min:.2f}'
|
||||||
|
else:
|
||||||
|
geoInfoItems['elevation (m)'] = f'{elev_min:.2f} - {elev_max:.2f}'
|
||||||
|
elif isinstance(geom, (Polygon, MultiPolygon)):
|
||||||
|
area = geom_reprojected.area
|
||||||
|
bounds = geom.bounds
|
||||||
|
geoInfoItems['longitude'] = f'{bounds[0]:.6f} - {bounds[2]:.6f}'
|
||||||
|
geoInfoItems['latitude'] = f'{bounds[1]:.6f} - {bounds[3]:.6f}'
|
||||||
|
geoInfoItems['area (sq. m)'] = f'{area:.1f} sq. m'
|
||||||
|
geoInfoItems['area (ha)'] = f'{area / 10000:.1f} ha'
|
||||||
|
geoInfoItems['area (acre)'] = f'{area / 4046.85643005078874:.1f} acres'
|
||||||
|
## TODO: elevation for MultiPolygon
|
||||||
|
if geom.has_z and not isinstance(geom, MultiPolygon):
|
||||||
|
if hasattr(geom, 'exterior'):
|
||||||
|
coords = geom.exterior.coords
|
||||||
|
else:
|
||||||
|
coords = geom.coords
|
||||||
|
elevations = [coord[2] for coord in coords]
|
||||||
|
elev_min = min(elevations)
|
||||||
|
elev_max = max(elevations)
|
||||||
|
if elev_min == elev_max:
|
||||||
|
geoInfoItems['elevation (m)'] = f'{elev_min:.2f}'
|
||||||
|
else:
|
||||||
|
geoInfoItems['elevation (m)'] = f'{elev_min:.2f} - {elev_max:.2f}'
|
||||||
|
|
||||||
|
feature_info_dict = {
|
||||||
|
'itemName': item.get('popup', f'Live: {store} #{id}'),
|
||||||
|
'geoInfoItems': geoInfoItems,
|
||||||
|
'surveyInfoItems': {
|
||||||
|
'Note': 'Live layers do not have survey info',
|
||||||
|
},
|
||||||
|
'infoItems': dict(item.drop(set(item.keys()).intersection(('geometry', 'popup')))),
|
||||||
|
'tags': {},
|
||||||
|
}
|
||||||
|
elif issubclass(model, BaseStore):
|
||||||
|
feature_info_dict = await model.get_item_params(id)
|
||||||
|
else:
|
||||||
|
## Not a live layer
|
||||||
|
try:
|
||||||
|
feature_info_dict = await registry.get_model_id_params(model, int(id))
|
||||||
|
except NotInRegistry:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
feature_info = FeatureInfo(
|
||||||
|
id=id,
|
||||||
|
itemName=feature_info_dict['itemName'],
|
||||||
|
geoInfoItems=[InfoItem(key=k, value=v)
|
||||||
|
for k, v in feature_info_dict['geoInfoItems'].items()],
|
||||||
|
surveyInfoItems=[InfoItem(key=k, value=v)
|
||||||
|
for k, v in feature_info_dict['surveyInfoItems'].items()],
|
||||||
|
infoItems=[InfoItem(key=k, value=v)
|
||||||
|
for k, v in feature_info_dict['infoItems'].items()],
|
||||||
|
tags=[InfoItem(key=k, value=v)
|
||||||
|
for k, v in feature_info_dict['tags'].items()],
|
||||||
|
graph=feature_info_dict.get('graph'),
|
||||||
|
)
|
||||||
|
if 'files' in feature_info_dict and feature_info_dict['files'] is not None:
|
||||||
|
feature_info.files = [
|
||||||
|
Attachment(name=k, path=v)
|
||||||
|
for k, v in feature_info_dict['files'].items()
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
feature_info.files = []
|
||||||
|
if 'images' in feature_info_dict and feature_info_dict['images'] is not None:
|
||||||
|
feature_info.images = [
|
||||||
|
Attachment(name=k, path=v)
|
||||||
|
for k, v in feature_info_dict['images'].items()
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
feature_info.images = []
|
||||||
|
if 'categorized_info_items' in feature_info_dict and feature_info_dict['categorized_info_items'] != None:
|
||||||
|
feature_info.categorizedInfoItems = [
|
||||||
|
InfoCategory(
|
||||||
|
name=name,
|
||||||
|
infoItems=[
|
||||||
|
InfoItem(key=k, value=v)
|
||||||
|
for k, v in info_items.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for name, info_items in feature_info_dict['categorized_info_items'].items()
|
||||||
|
]
|
||||||
|
feature_info.externalRecordUrl = feature_info_dict.get('externalRecordUrl')
|
||||||
|
return feature_info
|
||||||
|
|
||||||
# @api.get("/user-role")
|
# @api.get("/user-role")
|
||||||
# async def get_user_role_relation(
|
# async def get_user_role_relation(
|
||||||
# *, db_session: AsyncSession = Depends(get_db_session)
|
# *, db_session: AsyncSession = Depends(get_db_session)
|
||||||
|
|
|
@ -40,17 +40,29 @@ class BaseModel(SQLModel):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_df(cls, where=None, with_related=True, **kwargs) -> pd.DataFrame:
|
async def get_df(cls, *,
|
||||||
|
where=None, with_related=True, **kwargs
|
||||||
|
) -> pd.DataFrame:
|
||||||
return await cls._get_df(pandas_query, where=None, with_related=True, **kwargs)
|
return await cls._get_df(pandas_query, where=None, with_related=True, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gdf(cls, *, where=None, with_related=True, **kwargs) -> gpd.GeoDataFrame:
|
async def get_gdf(cls, *,
|
||||||
return await cls._get_df(geopandas_query, where=None, with_related=True, **kwargs)
|
where=None, with_related=True, **kwargs
|
||||||
|
) -> gpd.GeoDataFrame:
|
||||||
|
return await cls._get_df(geopandas_query,
|
||||||
|
where=None, with_related=True, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _get_df(cls, method, *, where=None, with_related=True, **kwargs) -> pd.DataFrame | gpd.GeoDataFrame:
|
async def _get_df(cls, method, *,
|
||||||
|
where=None, with_related=True, with_only_columns=[], **kwargs
|
||||||
|
) -> pd.DataFrame | gpd.GeoDataFrame:
|
||||||
async with db_session() as session:
|
async with db_session() as session:
|
||||||
query = select(cls)
|
if len(with_only_columns) == 0:
|
||||||
|
query = select(cls)
|
||||||
|
else:
|
||||||
|
columns = set(with_only_columns)
|
||||||
|
columns.add(*(col.name for col in cls.__table__.primary_key.columns))
|
||||||
|
query = select(*(getattr(cls, col) for col in columns))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
query.append_whereclause(where)
|
query.append_whereclause(where)
|
||||||
## Get the joined tables
|
## Get the joined tables
|
||||||
|
|
|
@ -9,7 +9,7 @@ import pandas as pd
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.survey import Surveyor, Equipment
|
from gisaf.models.survey import Surveyor, Equipment
|
||||||
from gisaf.models.project import Project
|
from gisaf.models.project import Project
|
||||||
from gisaf.models.metadata import gisaf_admin
|
from gisaf.models.metadata import gisaf_admin_table_args
|
||||||
|
|
||||||
|
|
||||||
re_file_import_record_date_expr = '^(\S+)-(\d\d\d\d)-(\d\d)-(\d\d).*$'
|
re_file_import_record_date_expr = '^(\S+)-(\d\d\d\d)-(\d\d)-(\d\d).*$'
|
||||||
|
@ -45,7 +45,7 @@ class FileImport(Model):
|
||||||
Give either url or path.
|
Give either url or path.
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'file_import'
|
__tablename__ = 'file_import'
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
url: str
|
url: str
|
||||||
|
@ -117,7 +117,7 @@ class FeatureImportData(Model):
|
||||||
Keep track of imported data, typically from shapefiles
|
Keep track of imported data, typically from shapefiles
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'feature_import_data'
|
__tablename__ = 'feature_import_data'
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
store: str = Field(index=True)
|
store: str = Field(index=True)
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
from sqlmodel import Field, SQLModel, MetaData, Relationship
|
from sqlmodel import Field, SQLModel, Relationship
|
||||||
|
|
||||||
|
from gisaf.models.metadata import gisaf_admin_table_args
|
||||||
|
|
||||||
from gisaf.models.metadata import gisaf_admin
|
|
||||||
|
|
||||||
class UserRoleLink(SQLModel, table=True):
|
class UserRoleLink(SQLModel, table=True):
|
||||||
metadata = gisaf_admin
|
|
||||||
__tablename__ = 'roles_users'
|
__tablename__ = 'roles_users'
|
||||||
|
__table_args__ = gisaf_admin_table_args
|
||||||
user_id: int | None = Field(
|
user_id: int | None = Field(
|
||||||
default=None, foreign_key="user.id", primary_key=True
|
default=None,
|
||||||
|
foreign_key=gisaf_admin_table_args['schema'] + '.user.id',
|
||||||
|
primary_key=True
|
||||||
)
|
)
|
||||||
role_id: int | None = Field(
|
role_id: int | None = Field(
|
||||||
default=None, foreign_key="role.id", primary_key=True
|
default=None,
|
||||||
|
foreign_key=gisaf_admin_table_args['schema'] + '.role.id',
|
||||||
|
primary_key=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +25,7 @@ class UserBase(SQLModel):
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase, table=True):
|
class User(UserBase, table=True):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
roles: list["Role"] = Relationship(back_populates="users",
|
roles: list["Role"] = Relationship(back_populates="users",
|
||||||
link_model=UserRoleLink)
|
link_model=UserRoleLink)
|
||||||
|
@ -40,7 +45,7 @@ class RoleWithDescription(RoleBase):
|
||||||
description: str | None
|
description: str | None
|
||||||
|
|
||||||
class Role(RoleWithDescription, table=True):
|
class Role(RoleWithDescription, table=True):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
users: list[User] = Relationship(back_populates="roles",
|
users: list[User] = Relationship(back_populates="roles",
|
||||||
link_model=UserRoleLink)
|
link_model=UserRoleLink)
|
||||||
|
|
|
@ -4,8 +4,8 @@ from sqlalchemy import String
|
||||||
from pydantic import computed_field, ConfigDict
|
from pydantic import computed_field, ConfigDict
|
||||||
from sqlmodel import Field, Relationship, JSON, TEXT
|
from sqlmodel import Field, Relationship, JSON, TEXT
|
||||||
|
|
||||||
from gisaf.models.metadata import gisaf_survey
|
|
||||||
from gisaf.database import BaseModel
|
from gisaf.database import BaseModel
|
||||||
|
from gisaf.models.metadata import gisaf_survey_table_args
|
||||||
|
|
||||||
mapbox_type_mapping = {
|
mapbox_type_mapping = {
|
||||||
'Point': 'symbol',
|
'Point': 'symbol',
|
||||||
|
@ -15,8 +15,8 @@ mapbox_type_mapping = {
|
||||||
|
|
||||||
|
|
||||||
class CategoryGroup(BaseModel, table=True):
|
class CategoryGroup(BaseModel, table=True):
|
||||||
metadata = gisaf_survey
|
|
||||||
__tablename__ = 'category_group'
|
__tablename__ = 'category_group'
|
||||||
|
__table_args__ = gisaf_survey_table_args
|
||||||
name: str | None = Field(sa_type=String(4), default=None, primary_key=True)
|
name: str | None = Field(sa_type=String(4), default=None, primary_key=True)
|
||||||
major: str
|
major: str
|
||||||
long_name: str
|
long_name: str
|
||||||
|
@ -28,8 +28,8 @@ class CategoryGroup(BaseModel, table=True):
|
||||||
|
|
||||||
|
|
||||||
class CategoryModelType(BaseModel, table=True):
|
class CategoryModelType(BaseModel, table=True):
|
||||||
metadata = gisaf_survey
|
|
||||||
__tablename__ = 'category_model_type'
|
__tablename__ = 'category_model_type'
|
||||||
|
__table_args__ = gisaf_survey_table_args
|
||||||
name: str | None = Field(default=None, primary_key=True)
|
name: str | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
@ -47,14 +47,15 @@ class CategoryBase(BaseModel):
|
||||||
domain: ClassVar[str] = 'V'
|
domain: ClassVar[str] = 'V'
|
||||||
description: str | None
|
description: str | None
|
||||||
group: str = Field(sa_type=String(4),
|
group: str = Field(sa_type=String(4),
|
||||||
foreign_key="category_group.name", index=True)
|
foreign_key=gisaf_survey_table_args['schema'] + ".category_group.name",
|
||||||
|
index=True)
|
||||||
minor_group_1: str = Field(sa_type=String(4), default='----')
|
minor_group_1: str = Field(sa_type=String(4), default='----')
|
||||||
minor_group_2: str = Field(sa_type=String(4), default='----')
|
minor_group_2: str = Field(sa_type=String(4), default='----')
|
||||||
status: str = Field(sa_type=String(1))
|
status: str = Field(sa_type=String(1))
|
||||||
custom: bool | None
|
custom: bool | None
|
||||||
auto_import: bool = True
|
auto_import: bool = True
|
||||||
gis_type: str = Field(sa_type=String(50),
|
gis_type: str = Field(sa_type=String(50),
|
||||||
foreign_key='category_model_type.name',
|
foreign_key=gisaf_survey_table_args['schema'] + '.category_model_type.name',
|
||||||
default='Point')
|
default='Point')
|
||||||
long_name: str | None = Field(sa_type=String(50))
|
long_name: str | None = Field(sa_type=String(50))
|
||||||
style: str | None = Field(sa_type=TEXT)
|
style: str | None = Field(sa_type=TEXT)
|
||||||
|
@ -105,7 +106,7 @@ class CategoryBase(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Category(CategoryBase, table=True):
|
class Category(CategoryBase, table=True):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
name: str | None = Field(default=None, primary_key=True)
|
name: str | None = Field(default=None, primary_key=True)
|
||||||
category_group: CategoryGroup = Relationship(back_populates="categories")
|
category_group: CategoryGroup = Relationship(back_populates="categories")
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from datetime import date, datetime
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
from functools import cached_property
|
||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -12,19 +13,16 @@ import geopandas as gpd # type: ignore
|
||||||
import shapely # type: ignore
|
import shapely # type: ignore
|
||||||
import pyproj
|
import pyproj
|
||||||
|
|
||||||
from sqlmodel import SQLModel, Field, Relationship
|
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from sqlmodel import select, Field, Relationship
|
||||||
from geoalchemy2.shape import from_shape
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
from sqlalchemy.dialects.postgresql import BIGINT
|
from sqlalchemy import BigInteger, MetaData, String, func, and_, text
|
||||||
from sqlalchemy import BigInteger, Column, MetaData, String, func, and_, text
|
|
||||||
from sqlalchemy.sql import sqltypes
|
from sqlalchemy.sql import sqltypes
|
||||||
|
from sqlalchemy.orm import declared_attr
|
||||||
from psycopg2.extensions import adapt
|
from psycopg2.extensions import adapt
|
||||||
|
from geoalchemy2.shape import from_shape
|
||||||
from geoalchemy2.types import Geometry, WKBElement
|
from geoalchemy2.types import Geometry, WKBElement
|
||||||
|
from shapely import wkb, from_wkb
|
||||||
from shapely import wkb
|
|
||||||
from shapely.geometry import mapping
|
from shapely.geometry import mapping
|
||||||
from shapely.ops import transform # type: ignore
|
from shapely.ops import transform # type: ignore
|
||||||
|
|
||||||
|
@ -35,15 +33,15 @@ from shapefile import (
|
||||||
POLYGON, POLYGONZ,
|
POLYGON, POLYGONZ,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from gisaf.database import db_session
|
from gisaf.database import db_session
|
||||||
from gisaf.config import conf
|
from gisaf.config import conf
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import survey, raw_survey
|
from gisaf.models.metadata import (
|
||||||
from gisaf.models.survey import Equipment, Surveyor, Accuracy
|
gisaf_survey_table_args, gisaf_admin_table_args, survey_table_args)
|
||||||
from gisaf.models.misc import Qml
|
from gisaf.models.misc import Qml
|
||||||
from gisaf.models.category import Category
|
from gisaf.models.category import Category
|
||||||
from gisaf.models.project import Project
|
# from gisaf.models.survey import Equipment, Surveyor, Accuracy
|
||||||
|
# from gisaf.models.project import Project
|
||||||
|
|
||||||
LOCALE_DATE_FORMAT = locale.nl_langinfo(locale.D_FMT)
|
LOCALE_DATE_FORMAT = locale.nl_langinfo(locale.D_FMT)
|
||||||
|
|
||||||
|
@ -82,14 +80,14 @@ class BaseSurveyModel(BaseModel):
|
||||||
- projected ('V_*')
|
- projected ('V_*')
|
||||||
"""
|
"""
|
||||||
id: int | None = Field(sa_type=BigInteger, primary_key=True, default=None)
|
id: int | None = Field(sa_type=BigInteger, primary_key=True, default=None)
|
||||||
equip_id: int = Field(foreign_key='equipment.id')
|
equip_id: int = Field(
|
||||||
# equipment: Equipment = Relationship()
|
foreign_key=gisaf_survey_table_args['schema'] + '.equipment.id')
|
||||||
srvyr_id: int = Field('surveyor.id')
|
srvyr_id: int = Field(
|
||||||
# surveyor: Surveyor = Relationship()
|
foreign_key=gisaf_survey_table_args['schema'] + '.surveyor.id')
|
||||||
accur_id: int = Field('accuracy.id')
|
accur_id: int = Field(
|
||||||
# accuracy: Accuracy = Relationship()
|
foreign_key=gisaf_survey_table_args['schema'] + '.accuracy.id')
|
||||||
project_id: int = Field('project.id')
|
project_id: int = Field(
|
||||||
# project: Project = Relationship()
|
foreign_key=gisaf_admin_table_args['schema'] + '.project.id')
|
||||||
|
|
||||||
orig_id: str
|
orig_id: str
|
||||||
date: date
|
date: date
|
||||||
|
@ -151,7 +149,7 @@ class SurveyModel(BaseSurveyModel):
|
||||||
"""
|
"""
|
||||||
Base mixin class for defining final (reprojected) survey data, with a status
|
Base mixin class for defining final (reprojected) survey data, with a status
|
||||||
"""
|
"""
|
||||||
metadata: ClassVar[MetaData] = survey
|
__table_args__ = survey_table_args
|
||||||
# status: str = Field(sa_type=String(1))
|
# status: str = Field(sa_type=String(1))
|
||||||
|
|
||||||
get_gdf_with_related: ClassVar[bool] = False
|
get_gdf_with_related: ClassVar[bool] = False
|
||||||
|
@ -182,6 +180,10 @@ class SurveyModel(BaseSurveyModel):
|
||||||
'gisaf_admin_project_skip_columns',
|
'gisaf_admin_project_skip_columns',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def __tablename__(cls) -> str:
|
||||||
|
return cls.__name__ # type: nocheck
|
||||||
|
|
||||||
async def get_survey_info(self):
|
async def get_survey_info(self):
|
||||||
info = await super(SurveyModel, self).get_survey_info()
|
info = await super(SurveyModel, self).get_survey_info()
|
||||||
if self.srvyr_id:
|
if self.srvyr_id:
|
||||||
|
@ -203,7 +205,7 @@ class SurveyModel(BaseSurveyModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def caption(self):
|
def caption(self):
|
||||||
return '{self.category.description} [{self.category.group}-{self.category.minor_group_1}] #{self.id:d}'.format(self=self)
|
return '{self.category.description} - {self.category.name} [{self.category.group}-{self.category.minor_group_1}] #{self.id:d}'.format(self=self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_popup(cls, df):
|
async def get_popup(cls, df):
|
||||||
|
@ -425,30 +427,28 @@ class GeoModelNoStatus(Model):
|
||||||
|
|
||||||
async def get_tags(self):
|
async def get_tags(self):
|
||||||
from gisaf.models.tags import Tags
|
from gisaf.models.tags import Tags
|
||||||
tags = await Tags.get_df(
|
async with db_session() as session:
|
||||||
where=and_(
|
query = select(Tags.tags).where(Tags.store == self.get_store_name(),
|
||||||
Tags.store == self.__class__.get_store_name(),
|
Tags.ref_id == self.id)
|
||||||
Tags.ref_id == self.id
|
tags = await session.exec(query)
|
||||||
),
|
return tags.one_or_none() or {}
|
||||||
with_only_columns=['tags']
|
|
||||||
)
|
|
||||||
if len(tags) > 0:
|
|
||||||
return tags.loc[0, 'tags']
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def shapely_geom(self):
|
def shapely_geom(self):
|
||||||
if not hasattr(self, '_shapely_geom'):
|
return from_wkb(self.geom.data)
|
||||||
if isinstance(self.geom, WKBElement):
|
|
||||||
bytes = self.geom.data
|
# @property
|
||||||
if bytes:
|
# def shapely_geom(self):
|
||||||
self._shapely_geom = wkb.loads(bytes)
|
# if not hasattr(self, '_shapely_geom'):
|
||||||
else:
|
# if isinstance(self.geom, WKBElement):
|
||||||
self._shapely_geom = None
|
# bytes = self.geom.data
|
||||||
else:
|
# if bytes:
|
||||||
self._shapely_geom = None
|
# self._shapely_geom = wkb.loads(bytes)
|
||||||
return self._shapely_geom
|
# else:
|
||||||
|
# self._shapely_geom = None
|
||||||
|
# else:
|
||||||
|
# self._shapely_geom = None
|
||||||
|
# return self._shapely_geom
|
||||||
|
|
||||||
def get_bgColor(self):
|
def get_bgColor(self):
|
||||||
"""
|
"""
|
||||||
|
@ -760,6 +760,7 @@ class GeoModelNoStatus(Model):
|
||||||
def get_attachment_base_dir(cls):
|
def get_attachment_base_dir(cls):
|
||||||
return Path(conf.attachments.base_dir) / cls.get_attachment_dir()
|
return Path(conf.attachments.base_dir) / cls.get_attachment_dir()
|
||||||
|
|
||||||
|
|
||||||
class GeoModel(GeoModelNoStatus):
|
class GeoModel(GeoModelNoStatus):
|
||||||
status: ClassVar[str] = 'E'
|
status: ClassVar[str] = 'E'
|
||||||
"""
|
"""
|
||||||
|
@ -1061,7 +1062,7 @@ class RawSurveyBaseModel(BaseSurveyModel, GeoPointModelNoStatus):
|
||||||
"""
|
"""
|
||||||
Abstract base class for category based raw survey point models
|
Abstract base class for category based raw survey point models
|
||||||
"""
|
"""
|
||||||
metadata: ClassVar[MetaData] = raw_survey
|
# metadata: ClassVar[MetaData] = raw_survey
|
||||||
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POINTZ', dimension=3,
|
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POINTZ', dimension=3,
|
||||||
srid=conf.geo.raw_survey.srid))
|
srid=conf.geo.raw_survey.srid))
|
||||||
status: str = Field(sa_type=String(1))
|
status: str = Field(sa_type=String(1))
|
||||||
|
|
|
@ -3,11 +3,11 @@ from typing import Any
|
||||||
from sqlmodel import Field, String, JSON, Relationship
|
from sqlmodel import Field, String, JSON, Relationship
|
||||||
|
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import gisaf_map
|
from gisaf.models.metadata import gisaf_map_table_args
|
||||||
|
|
||||||
|
|
||||||
class BaseStyle(Model):
|
class BaseStyle(Model):
|
||||||
metadata = gisaf_map
|
__table_args__ = gisaf_map_table_args
|
||||||
__tablename__ = 'map_base_style'
|
__tablename__ = 'map_base_style'
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
@ -26,7 +26,7 @@ class BaseStyle(Model):
|
||||||
|
|
||||||
|
|
||||||
class BaseMap(Model):
|
class BaseMap(Model):
|
||||||
metadata = gisaf_map
|
__table_args__ = gisaf_map_table_args
|
||||||
__tablename__ = 'base_map'
|
__tablename__ = 'base_map'
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
@ -43,7 +43,7 @@ class BaseMap(Model):
|
||||||
|
|
||||||
|
|
||||||
class BaseMapLayer(Model):
|
class BaseMapLayer(Model):
|
||||||
metadata = gisaf_map
|
__table_args__ = gisaf_map_table_args
|
||||||
__tablename__ = 'base_map_layer'
|
__tablename__ = 'base_map_layer'
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from sqlmodel import MetaData
|
|
||||||
|
|
||||||
from gisaf.config import conf
|
from gisaf.config import conf
|
||||||
|
|
||||||
gisaf = MetaData(schema='gisaf')
|
gisaf_table_args = dict(schema= 'gisaf')
|
||||||
gisaf_survey = MetaData(schema='gisaf_survey')
|
gisaf_survey_table_args = dict(schema='gisaf_survey')
|
||||||
gisaf_admin = MetaData(schema='gisaf_admin')
|
gisaf_admin_table_args = dict(schema='gisaf_admin')
|
||||||
gisaf_map = MetaData(schema='gisaf_map')
|
gisaf_map_table_args = dict(schema='gisaf_map')
|
||||||
raw_survey = MetaData(schema=conf.survey.db_schema_raw)
|
raw_survey_table_args = dict(schema=conf.survey.db_schema_raw)
|
||||||
survey = MetaData(schema=conf.survey.db_schema)
|
survey_table_args = dict(schema=conf.survey.db_schema)
|
|
@ -5,7 +5,7 @@ from pydantic import ConfigDict
|
||||||
from sqlmodel import Field, JSON, Column
|
from sqlmodel import Field, JSON, Column
|
||||||
|
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import gisaf_map
|
from gisaf.models.metadata import gisaf_map_table_args
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class Qml(Model):
|
||||||
Model for storing qml (QGis style)
|
Model for storing qml (QGis style)
|
||||||
"""
|
"""
|
||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
metadata = gisaf_map
|
__table_args__ = gisaf_map_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Model(BaseModel):
|
||||||
if hasattr(cls, '__table__'):
|
if hasattr(cls, '__table__'):
|
||||||
return cls.__table__.fullname
|
return cls.__table__.fullname
|
||||||
elif hasattr(cls, '__table_args__') and 'schema' in cls.__table_args__:
|
elif hasattr(cls, '__table_args__') and 'schema' in cls.__table_args__:
|
||||||
return f"{cls.__table_args__.schema}.{cls.__tablename__}"
|
return f"{cls.__table_args__['schema']}.{cls.__tablename__}"
|
||||||
else:
|
else:
|
||||||
return f'{cls.metadata.schema}.{cls.__tablename__}'
|
return f'{cls.metadata.schema}.{cls.__tablename__}'
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ from shapely.geometry import Point
|
||||||
|
|
||||||
from gisaf.config import conf
|
from gisaf.config import conf
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import gisaf_admin
|
from gisaf.models.metadata import gisaf_admin_table_args
|
||||||
|
|
||||||
class Project(Model, table=True):
|
class Project(Model, table=True):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
|
|
@ -5,10 +5,10 @@ from gisaf.models.models_base import Model
|
||||||
from gisaf.models.geo_models_base import GeoPointMModel, BaseSurveyModel
|
from gisaf.models.geo_models_base import GeoPointMModel, BaseSurveyModel
|
||||||
from gisaf.models.project import Project
|
from gisaf.models.project import Project
|
||||||
from gisaf.models.category import Category
|
from gisaf.models.category import Category
|
||||||
from gisaf.models.metadata import gisaf_survey
|
from gisaf.models.metadata import gisaf_survey_table_args
|
||||||
|
|
||||||
class RawSurveyModel(BaseSurveyModel, GeoPointMModel):
|
class RawSurveyModel(BaseSurveyModel, GeoPointMModel):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
__tablename__ = 'raw_survey'
|
__tablename__ = 'raw_survey'
|
||||||
hidden: ClassVar[bool] = True
|
hidden: ClassVar[bool] = True
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class OriginRawPoint(Model):
|
||||||
for each line and polygon shape
|
for each line and polygon shape
|
||||||
Filled when importing shapefiles
|
Filled when importing shapefiles
|
||||||
"""
|
"""
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
__tablename__ = 'origin_raw_point'
|
__tablename__ = 'origin_raw_point'
|
||||||
|
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
|
@ -4,11 +4,11 @@ from sqlalchemy import BigInteger, String
|
||||||
from sqlmodel import Field
|
from sqlmodel import Field
|
||||||
|
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import gisaf_admin
|
from gisaf.models.metadata import gisaf_admin_table_args
|
||||||
|
|
||||||
|
|
||||||
class Reconciliation(Model):
|
class Reconciliation(Model):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
@ -21,7 +21,7 @@ class Reconciliation(Model):
|
||||||
|
|
||||||
|
|
||||||
class StatusChange(Model):
|
class StatusChange(Model):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
__tablename__ = 'status_change'
|
__tablename__ = 'status_change'
|
||||||
|
|
||||||
id: int = Field(primary_key=True, sa_type=BigInteger,
|
id: int = Field(primary_key=True, sa_type=BigInteger,
|
||||||
|
@ -34,7 +34,7 @@ class StatusChange(Model):
|
||||||
|
|
||||||
|
|
||||||
class FeatureDeletion(Model):
|
class FeatureDeletion(Model):
|
||||||
metadata = gisaf_admin
|
__table_args__ = gisaf_admin_table_args
|
||||||
__tablename__ = 'feature_deletion'
|
__tablename__ = 'feature_deletion'
|
||||||
|
|
||||||
id: int = Field(BigInteger, primary_key=True,
|
id: int = Field(BigInteger, primary_key=True,
|
||||||
|
|
|
@ -3,11 +3,11 @@ from enum import Enum
|
||||||
from sqlmodel import Field, Relationship
|
from sqlmodel import Field, Relationship
|
||||||
|
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
from gisaf.models.metadata import gisaf_survey
|
from gisaf.models.metadata import gisaf_survey_table_args
|
||||||
|
|
||||||
|
|
||||||
class Accuracy(Model, table=True):
|
class Accuracy(Model, table=True):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
@ -25,7 +25,7 @@ class Accuracy(Model, table=True):
|
||||||
|
|
||||||
|
|
||||||
class Surveyor(Model, table=True):
|
class Surveyor(Model, table=True):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
@ -42,7 +42,7 @@ class Surveyor(Model, table=True):
|
||||||
|
|
||||||
|
|
||||||
class Equipment(Model, table=True):
|
class Equipment(Model, table=True):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
@ -62,17 +62,17 @@ class GeometryType(str, Enum):
|
||||||
line_work = 'Line_work'
|
line_work = 'Line_work'
|
||||||
|
|
||||||
class AccuracyEquimentSurveyorMapping(Model, table=True):
|
class AccuracyEquimentSurveyorMapping(Model, table=True):
|
||||||
metadata = gisaf_survey
|
__table_args__ = gisaf_survey_table_args
|
||||||
__tablename__ = 'accuracy_equiment_surveyor_mapping'
|
__tablename__ = 'accuracy_equiment_surveyor_mapping'
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
|
||||||
id: int | None= Field(default=None, primary_key=True)
|
id: int | None= Field(default=None, primary_key=True)
|
||||||
surveyor_id: int = Field(foreign_key='surveyor.id', index=True)
|
surveyor_id: int = Field(foreign_key=gisaf_survey_table_args['schema'] + '.surveyor.id', index=True)
|
||||||
equipment_id: int = Field(foreign_key='equipment.id', index=True)
|
equipment_id: int = Field(foreign_key=gisaf_survey_table_args['schema'] + '.equipment.id', index=True)
|
||||||
geometry_type: GeometryType = Field(default='Point', index=True)
|
geometry_type: GeometryType = Field(default='Point', index=True)
|
||||||
accuracy_id: int = Field(foreign_key='accuracy.id')
|
accuracy_id: int = Field(foreign_key=gisaf_survey_table_args['schema'] + '.accuracy.id')
|
||||||
surveyor: Surveyor = Relationship()
|
surveyor: Surveyor = Relationship()
|
||||||
accuracy: Accuracy = Relationship()
|
accuracy: Accuracy = Relationship()
|
||||||
equipment: Equipment = Relationship()
|
equipment: Equipment = Relationship()
|
||||||
|
|
|
@ -5,11 +5,11 @@ from sqlalchemy.dialects.postgresql import HSTORE
|
||||||
from sqlmodel import Field, SQLModel, MetaData, JSON, TEXT, Relationship, Column
|
from sqlmodel import Field, SQLModel, MetaData, JSON, TEXT, Relationship, Column
|
||||||
from pydantic import computed_field
|
from pydantic import computed_field
|
||||||
|
|
||||||
from gisaf.models.metadata import gisaf
|
from gisaf.models.metadata import gisaf_table_args
|
||||||
from gisaf.models.geo_models_base import GeoPointModel
|
from gisaf.models.geo_models_base import GeoPointModel
|
||||||
|
|
||||||
class Tags(GeoPointModel, table=True):
|
class Tags(GeoPointModel, table=True):
|
||||||
metadata = gisaf
|
__table_args__ = gisaf_table_args
|
||||||
hidden: ClassVar[bool] = True
|
hidden: ClassVar[bool] = True
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
@ -29,7 +29,7 @@ class Tags(GeoPointModel, table=True):
|
||||||
|
|
||||||
|
|
||||||
class TagKey(SQLModel, table=True):
|
class TagKey(SQLModel, table=True):
|
||||||
metadata = gisaf
|
__table_args__ = gisaf_table_args
|
||||||
## CREATE TABLE gisaf.tagkey (key VARCHAR(255) primary key);
|
## CREATE TABLE gisaf.tagkey (key VARCHAR(255) primary key);
|
||||||
|
|
||||||
class Admin:
|
class Admin:
|
||||||
|
|
|
@ -3,20 +3,81 @@ from pydantic import BaseModel
|
||||||
class ActionResult(BaseModel):
|
class ActionResult(BaseModel):
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
class ActionResults(BaseModel):
|
class ActionResults(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
message: str
|
message: str
|
||||||
actionResults: list[ActionResult]
|
actionResults: list[ActionResult]
|
||||||
|
|
||||||
|
|
||||||
class FormField(BaseModel):
|
class FormField(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
|
||||||
class ModelAction(BaseModel):
|
class ModelAction(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
icon: str
|
icon: str
|
||||||
formFields: list[FormField]
|
formFields: list[FormField]
|
||||||
|
|
||||||
|
|
||||||
class DataProvider(BaseModel):
|
class DataProvider(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
values: list[str]
|
values: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class InfoItem(BaseModel):
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
class InfoCategory(BaseModel):
|
||||||
|
name: str
|
||||||
|
infoItems: list[InfoItem]
|
||||||
|
|
||||||
|
|
||||||
|
class PlotBgShape(BaseModel):
|
||||||
|
name: str
|
||||||
|
valueTop: float
|
||||||
|
valueBottom: float
|
||||||
|
color: str
|
||||||
|
|
||||||
|
|
||||||
|
class PlotBaseLine(BaseModel):
|
||||||
|
name: str
|
||||||
|
value: float
|
||||||
|
color: str
|
||||||
|
|
||||||
|
|
||||||
|
class PlotParams(BaseModel):
|
||||||
|
baseLines: list[PlotBaseLine]
|
||||||
|
bgShape: list[PlotBgShape]
|
||||||
|
barBase: float
|
||||||
|
|
||||||
|
|
||||||
|
class Attachment(BaseModel):
|
||||||
|
name: str
|
||||||
|
path: str
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureInfo(BaseModel):
|
||||||
|
id: str
|
||||||
|
itemName: str
|
||||||
|
geoInfoItems: list[InfoItem] = []
|
||||||
|
surveyInfoItems: list[InfoItem] = []
|
||||||
|
infoItems: list[InfoItem] = []
|
||||||
|
categorizedInfoItems: list[InfoCategory] = []
|
||||||
|
tags: list[InfoItem] = []
|
||||||
|
graph: str | None = None
|
||||||
|
plotParams: PlotParams | None = None
|
||||||
|
files: list[Attachment] = []
|
||||||
|
images: list[Attachment] = []
|
||||||
|
externalRecordUrl: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class MapboxPaint(BaseModel):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MapboxLayout(BaseModel):
|
||||||
|
...
|
|
@ -9,10 +9,10 @@ from importlib.metadata import entry_points
|
||||||
from typing import Any, ClassVar, Literal
|
from typing import Any, ClassVar, Literal
|
||||||
|
|
||||||
from pydantic import create_model
|
from pydantic import create_model
|
||||||
from pydantic_core import PydanticUndefined
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload, joinedload
|
||||||
from sqlmodel import SQLModel, select, inspect
|
from sqlalchemy.exc import NoResultFound
|
||||||
|
from sqlmodel import SQLModel, select, inspect, Relationship
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
@ -30,11 +30,14 @@ from gisaf.models.geo_models_base import (
|
||||||
GeoLineSurveyModel,
|
GeoLineSurveyModel,
|
||||||
GeoPolygonSurveyModel,
|
GeoPolygonSurveyModel,
|
||||||
)
|
)
|
||||||
|
from gisaf.models.survey import Equipment, Surveyor, Accuracy
|
||||||
|
from gisaf.models.project import Project
|
||||||
from gisaf.utils import ToMigrate
|
from gisaf.utils import ToMigrate
|
||||||
from gisaf.models.category import Category, CategoryGroup
|
from gisaf.models.category import Category, CategoryGroup
|
||||||
from gisaf.database import db_session
|
from gisaf.database import db_session
|
||||||
from gisaf import models
|
from gisaf import models
|
||||||
from gisaf.models.metadata import survey, raw_survey
|
from gisaf.models.metadata import (
|
||||||
|
gisaf_survey_table_args, raw_survey_table_args, survey_table_args)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -139,8 +142,8 @@ class ModelRegistry:
|
||||||
|
|
||||||
## Use pydantic create_model, supported by SQLModel
|
## Use pydantic create_model, supported by SQLModel
|
||||||
## See https://github.com/tiangolo/sqlmodel/issues/377
|
## See https://github.com/tiangolo/sqlmodel/issues/377
|
||||||
store_name = f'{survey.schema}.{category.table_name}'
|
store_name = f"{survey_table_args['schema']}.{category.table_name}"
|
||||||
raw_store_name = f'{raw_survey.schema}.RAW_{category.table_name}'
|
raw_store_name = f"{raw_survey_table_args['schema']}.RAW_{category.table_name}"
|
||||||
raw_survey_field_definitions = {
|
raw_survey_field_definitions = {
|
||||||
## FIXME: RawSurveyBaseModel.category should be a Category, not category.name
|
## FIXME: RawSurveyBaseModel.category should be a Category, not category.name
|
||||||
'category_name': (ClassVar[str], category.name),
|
'category_name': (ClassVar[str], category.name),
|
||||||
|
@ -157,7 +160,6 @@ class ModelRegistry:
|
||||||
__model_name=category.raw_survey_table_name,
|
__model_name=category.raw_survey_table_name,
|
||||||
__cls_kwargs__={
|
__cls_kwargs__={
|
||||||
'table': True,
|
'table': True,
|
||||||
'__tablename__': category.raw_survey_table_name,
|
|
||||||
},
|
},
|
||||||
**raw_survey_field_definitions
|
**raw_survey_field_definitions
|
||||||
)
|
)
|
||||||
|
@ -173,10 +175,15 @@ class ModelRegistry:
|
||||||
if model_class:
|
if model_class:
|
||||||
survey_field_definitions = {
|
survey_field_definitions = {
|
||||||
'category_name': (ClassVar[str], category.name),
|
'category_name': (ClassVar[str], category.name),
|
||||||
|
'category': (ClassVar[Category], category),
|
||||||
'group_name': (ClassVar[str], category.category_group.name),
|
'group_name': (ClassVar[str], category.category_group.name),
|
||||||
'raw_store_name': (ClassVar[str], raw_store_name),
|
'raw_store_name': (ClassVar[str], raw_store_name),
|
||||||
'viewable_role': (ClassVar[str], category.viewable_role),
|
'viewable_role': (ClassVar[str], category.viewable_role),
|
||||||
'symbol': (ClassVar[str], category.symbol),
|
'symbol': (ClassVar[str], category.symbol),
|
||||||
|
'equipment': (Equipment, Relationship()),
|
||||||
|
'surveyor': (Surveyor, Relationship()),
|
||||||
|
'accuracy': (Accuracy, Relationship()),
|
||||||
|
'project': (Project, Relationship()),
|
||||||
#'raw_model': (str, self.raw_survey_models.get(raw_store_name)),
|
#'raw_model': (str, self.raw_survey_models.get(raw_store_name)),
|
||||||
# 'icon': (str, f'{survey.schema}-{category.table_name}'),
|
# 'icon': (str, f'{survey.schema}-{category.table_name}'),
|
||||||
}
|
}
|
||||||
|
@ -185,7 +192,6 @@ class ModelRegistry:
|
||||||
__model_name=category.table_name,
|
__model_name=category.table_name,
|
||||||
__cls_kwargs__={
|
__cls_kwargs__={
|
||||||
'table': True,
|
'table': True,
|
||||||
'__tablename__': category.table_name,
|
|
||||||
},
|
},
|
||||||
**survey_field_definitions,
|
**survey_field_definitions,
|
||||||
)
|
)
|
||||||
|
@ -335,7 +341,16 @@ class ModelRegistry:
|
||||||
"""
|
"""
|
||||||
if not model:
|
if not model:
|
||||||
return {}
|
return {}
|
||||||
item = await model.load(**model.get_join_with()).query.where(model.id==id).gino.first()
|
async with db_session() as session:
|
||||||
|
query = select(model).where(model.id == id).options(
|
||||||
|
*(joinedload(jt) for jt in model.selectinload()))
|
||||||
|
result = await session.exec(query)
|
||||||
|
try:
|
||||||
|
item = result.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise NotInRegistry
|
||||||
|
|
||||||
|
# item = await model.load(**model.get_join_with()).query.where(model.id==id).gino.first()
|
||||||
if not item:
|
if not item:
|
||||||
return {}
|
return {}
|
||||||
resp = {}
|
resp = {}
|
||||||
|
@ -386,7 +401,7 @@ class ModelRegistry:
|
||||||
if category.minor_group_2 != '----':
|
if category.minor_group_2 != '----':
|
||||||
fragments.append(category.minor_group_2)
|
fragments.append(category.minor_group_2)
|
||||||
return '.'.join([
|
return '.'.join([
|
||||||
survey.schema,
|
survey_table_args['schema'],
|
||||||
'_'.join(fragments)
|
'_'.join(fragments)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -126,32 +126,32 @@ class NumpyEncoder(JSONEncoder):
|
||||||
# headers=headers, content_type=content_type, **kwargs)
|
# headers=headers, content_type=content_type, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_join_with(cls, recursive=True):
|
# def get_join_with(cls, recursive=True):
|
||||||
"""
|
# """
|
||||||
Helper function for loading related tables with a Gino loader (left outer join)
|
# Helper function for loading related tables with a Gino loader (left outer join)
|
||||||
Should work recursively...
|
# Should work recursively...
|
||||||
Eg:
|
# Eg:
|
||||||
cls.load(**get_join_with(cls)).query.gino.all()
|
# cls.load(**get_join_with(cls)).query.gino.all()
|
||||||
:param cls:
|
# :param cls:
|
||||||
:return:
|
# :return:
|
||||||
"""
|
# """
|
||||||
if hasattr(cls, 'dyn_join_with'):
|
# if hasattr(cls, 'dyn_join_with'):
|
||||||
joins = cls.dyn_join_with()
|
# joins = cls.dyn_join_with()
|
||||||
else:
|
# else:
|
||||||
joins = {}
|
# joins = {}
|
||||||
if hasattr(cls, '_join_with'):
|
# if hasattr(cls, '_join_with'):
|
||||||
joins.update(cls._join_with)
|
# joins.update(cls._join_with)
|
||||||
if not recursive:
|
# if not recursive:
|
||||||
return joins
|
# return joins
|
||||||
recursive_joins = {}
|
# recursive_joins = {}
|
||||||
for name, join in joins.items():
|
# for name, join in joins.items():
|
||||||
more_joins = get_join_with(join)
|
# more_joins = get_join_with(join)
|
||||||
if more_joins:
|
# if more_joins:
|
||||||
aliased = {name: join.alias() for name, join in more_joins.items()}
|
# aliased = {name: join.alias() for name, join in more_joins.items()}
|
||||||
recursive_joins[name] = join.load(**aliased)
|
# recursive_joins[name] = join.load(**aliased)
|
||||||
else:
|
# else:
|
||||||
recursive_joins[name] = join
|
# recursive_joins[name] = join
|
||||||
return recursive_joins
|
# return recursive_joins
|
||||||
|
|
||||||
def get_joined_query(cls):
|
def get_joined_query(cls):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue