Functional foreign key account selector and path generator

This commit is contained in:
2025-02-02 20:12:55 +01:00
parent fd92c57eb5
commit 1e8731d78b
4 changed files with 56 additions and 22 deletions

View File

@@ -6,6 +6,7 @@ from fastapi_filter.contrib.sqlalchemy import Filter
from pydantic.json_schema import SkipJsonSchema from pydantic.json_schema import SkipJsonSchema
from sqlmodel import Field, SQLModel, select, Relationship from sqlmodel import Field, SQLModel, select, Relationship
from pydantic import Field as PydField from pydantic import Field as PydField
from sqlalchemy.sql import text
class AccountBase(SQLModel): class AccountBase(SQLModel):
@@ -13,7 +14,7 @@ class AccountBase(SQLModel):
parent_account_id: Optional[UUID] = Field(default=None, foreign_key="account.id") parent_account_id: Optional[UUID] = Field(default=None, foreign_key="account.id")
class AccountBaseId(AccountBase): class AccountBaseId(AccountBase):
id: UUID | None = Field(default_factory=uuid4, primary_key=True) id: UUID | None = Field(default=uuid4(), primary_key=True)
type: str = Field(index=True) type: str = Field(index=True)
path: str = Field(index=True) path: str = Field(index=True)
@@ -25,11 +26,15 @@ class Account(AccountBaseId, table=True):
children_accounts: list["Account"] = Relationship(back_populates='parent_account') children_accounts: list["Account"] = Relationship(back_populates='parent_account')
def get_child_path(self): def get_child_path(self):
return f"{self.path}/{self.name}" return f"{self.path}{self.name}/"
def get_root_path(self): def get_root_path(self):
root = "/Categories" if self.is_category() else "/Accounts" root = "/Categories" if self.is_category() else "/Accounts"
return f"{root}/{self.type}" return f"{root}/{self.type}/"
def update_children_path(self, session, old_path):
request = f"UPDATE {self.__tablename__} SET path=REPLACE(path, '{old_path}', '{self.path}') WHERE path LIKE '{old_path}{self.name }/%'"
session.exec(text(request))
def is_category(self): def is_category(self):
return self.type in [v.value for v in CategoryType] return self.type in [v.value for v in CategoryType]
@@ -42,8 +47,17 @@ class Account(AccountBaseId, table=True):
return parent.get_child_path() return parent.get_child_path()
@classmethod @classmethod
def schema_to_model(cls, session, schema): def schema_to_model(cls, session, schema, model=None):
model = cls.model_validate(schema)
try:
if model:
model = cls.model_validate(model, update=schema)
else:
schema['path'] = ""
model = cls.model_validate(schema)
except Exception as e:
print(e)
model.path = model.get_path(session) model.path = model.get_path(session)
return model return model
@@ -76,19 +90,13 @@ class Account(AccountBaseId, table=True):
def get(cls, session, account_id): def get(cls, session, account_id):
return session.get(Account, account_id) return session.get(Account, account_id)
def update_children_path(self, session):
for child in self.children_accounts:
child.path = self.get_child_path()
session.add(child)
child.update_children_path(session)
@classmethod @classmethod
def update(cls, session, account_db, account_data): def update(cls, session, account_db, account_data):
previous_path = account_db.path previous_path = account_db.path
account_db.sqlmodel_update(cls.schema_to_model(session, account_data)) account_db.sqlmodel_update(cls.schema_to_model(session, account_data, account_db))
session.add(account_db)
if previous_path != account_db.path: if previous_path != account_db.path:
account_db.update_children_path(session, account_db) account_db.update_children_path(session, previous_path)
session.add(account_db)
session.commit() session.commit()
session.refresh(account_db) session.refresh(account_db)
return account_db return account_db
@@ -137,7 +145,7 @@ class Liability(Enum):
class AccountWrite(AccountBase): class AccountWrite(AccountBase):
type: Asset | Liability = Field() type: Asset | Liability = Field()
path: SkipJsonSchema[str] = Field(default="") path: SkipJsonSchema[str] = Field(default="")
parent_account_id: UUID = PydField(default=None, json_schema_extra={ parent_account_id: UUID | None = PydField(default=None, json_schema_extra={
"foreign_key": { "foreign_key": {
"reference": { "reference": {
"resource": "accounts", "resource": "accounts",

View File

@@ -23,6 +23,21 @@ const UnionEnumField = (props: FieldProps) => {
} = props; } = props;
const enumOptions: any[] = [] const enumOptions: any[] = []
if (options.length == 2 && (options[0].type == "null" || options[1].type == "null")) {
const { SchemaField: _SchemaField } = registry.fields;
let opt_schema = {...schema}
delete(opt_schema.anyOf)
if (options[0].type == "null") {
opt_schema = {...opt_schema, ...options[1]}
} else if (options[1].type == "null") {
opt_schema = {...opt_schema, ...options[0]}
}
return <_SchemaField {...props} schema={opt_schema} uiSchema={uiSchema} />
}
for (let opt of options) { for (let opt of options) {
if (!opt.hasOwnProperty('enum')) { if (!opt.hasOwnProperty('enum')) {
return (<AnyOfField {...props} />) return (<AnyOfField {...props} />)

View File

@@ -2,7 +2,7 @@ import { WidgetProps } from '@rjsf/utils';
import { Autocomplete } from "@mui/material"; import { Autocomplete } from "@mui/material";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { useList, useOne } from "@refinedev/core"; import {BaseRecord, useList, useOne} from "@refinedev/core";
export const ForeignKeyWidget = (props: WidgetProps) => { export const ForeignKeyWidget = (props: WidgetProps) => {
const resource = props.schema.foreign_key.reference.resource const resource = props.schema.foreign_key.reference.resource
@@ -10,7 +10,7 @@ export const ForeignKeyWidget = (props: WidgetProps) => {
const valueResult = useOne({ const valueResult = useOne({
resource: resource, resource: resource,
id: props.value != "" ? props.value : undefined id: props.value != null ? props.value : undefined
}); });
const [inputValue, setInputValue] = useState<string>(""); const [inputValue, setInputValue] = useState<string>("");
@@ -30,6 +30,13 @@ export const ForeignKeyWidget = (props: WidgetProps) => {
}); });
const options = listResult.data?.data || []; const options = listResult.data?.data || [];
if (! props.required) {
const empty_option: BaseRecord = {
id: null
}
empty_option[labelField] = "(None)"
options.unshift(empty_option);
}
const isLoading = listResult.isLoading || valueResult.isLoading; const isLoading = listResult.isLoading || valueResult.isLoading;
if(! selectedValue && valueResult.data) { if(! selectedValue && valueResult.data) {
@@ -40,9 +47,9 @@ export const ForeignKeyWidget = (props: WidgetProps) => {
<Autocomplete <Autocomplete
value={selectedValue} value={selectedValue}
onChange={(event, newValue) => { onChange={(event, newValue) => {
setSelectedValue(newValue) setSelectedValue(newValue);
props.onChange(newValue ? newValue.id : "") props.onChange(newValue ? newValue.id : null);
return true return true;
}} }}
//inputValue={inputValue} //inputValue={inputValue}
onInputChange={(event, newInputValue) => setInputValue(newInputValue)} onInputChange={(event, newInputValue) => setInputValue(newInputValue)}

View File

@@ -33,11 +33,15 @@ function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
} else if (is_union(prop)) { } else if (is_union(prop)) {
const union = prop.hasOwnProperty("oneOf") ? prop.oneOf : prop.anyOf; const union = prop.hasOwnProperty("oneOf") ? prop.oneOf : prop.anyOf;
for (let i in union) { for (let i in union) {
resolveReference(rawSchemas, resource, union[i]); if (is_reference(union[i])) {
resolveReference(rawSchemas, resource, union[i]);
}
} }
} else if (is_enum(prop)) { } else if (is_enum(prop)) {
for (let i in prop.allOf) { for (let i in prop.allOf) {
resolveReference(rawSchemas, resource, prop.allOf[i]); if (is_reference(prop.allOf[i])) {
resolveReference(rawSchemas, resource, prop.allOf[i]);
}
} }
} else if (is_array(prop) && is_reference(prop.items)) { } else if (is_array(prop) && is_reference(prop.items)) {
resolveReference(rawSchemas, resource, prop.items); resolveReference(rawSchemas, resource, prop.items);