Compare commits

..

3 Commits

Author SHA1 Message Date
35448385c5 Adding foreign key to auto forms 2025-01-20 11:31:57 +01:00
fe84d6de2f Updating projet config 2025-01-20 11:30:47 +01:00
5dd885a061 Updating IDE config 2025-01-20 11:29:20 +01:00
10 changed files with 111 additions and 8 deletions

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
api/app/database.db
api/password
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -4,8 +4,9 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/api/app" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/api/app" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/api/.venv" /> <excludeFolder url="file://$MODULE_DIR$/api/.venv" />
<excludeFolder url="file://$MODULE_DIR$/api/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.12 virtualenv at ~/projects/dev/python/budget_forecast/api/venv" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 (budget-forecast)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.12 (budget_forecast)" /> <option name="sdkName" value="Python 3.12 (budget_forecast)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 virtualenv at ~/projects/dev/python/budget_forecast/api/venv" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (budget-forecast)" project-jdk-type="Python SDK" />
</project> </project>

2
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/gui/app" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -2,16 +2,17 @@ from uuid import UUID, uuid4
from enum import Enum from enum import Enum
from sqlmodel import Field, SQLModel, select from sqlmodel import Field, SQLModel, select
from pydantic import Field as PydField
from category.models import CategoryRead from category.models import CategoryRead
class AccountType(Enum): class AccountType(Enum):
Asset = "Asset" # < Denotes a generic asset account. Asset = "Asset" # < Denotes a generic asset account.
Checkings = "Checkings" # < Standard checking account Checkings = "Checkings" # < Standard checking account
Savings = "Savings" # < Typical savings account Savings = "Savings" # < Typical savings account
Cash = "Cash" # < Denotes a shoe-box or pillowcase stuffed with cash Cash = "Cash" # < Denotes a shoe-box or pillowcase stuffed with cash
Liability = "Liability" # < Denotes a generic liability account. Liability = "Liability" # < Denotes a generic liability account.
CreditCard = "CreditCard" # < Credit card accounts CreditCard = "CreditCard" # < Credit card accounts
Loan = "Loan" # < Loan and mortgage accounts (liability) Loan = "Loan" # < Loan and mortgage accounts (liability)
@@ -69,10 +70,19 @@ class Account(AccountBaseId, table=True):
session.commit() session.commit()
class AccountRead(AccountBaseId): class AccountRead(AccountBaseId):
default_category: CategoryRead # default_category: CategoryRead
pass
class AccountWrite(AccountBase): class AccountWrite(AccountBase):
pass default_category_id: UUID = PydField(default=None, json_schema_extra={
"foreign_key": {
"reference": {
"resource": "categories",
"schema": "CategoryRead",
"label": "name"
}
}
})
class AccountCreate(AccountWrite): class AccountCreate(AccountWrite):
pass pass

7
api/app/requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
uvicorn
sqlmodel
alembic
fastapi
fastapi-pagination
fastapi-users[sqlmodel]
fastapi-users-db-sqlmodel

View File

@@ -1,11 +1,11 @@
{ {
"name": "auth-material-ui", "name": "budget-forecast-gui",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "auth-material-ui", "name": "budget-forecast-gui",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@emotion/react": "^11.8.2", "@emotion/react": "^11.8.2",

View File

@@ -1,8 +1,11 @@
import validator from "@rjsf/validator-ajv8"; import validator from "@rjsf/validator-ajv8";
import Form from "@rjsf/mui"; import Form from "@rjsf/mui";
import { RegistryWidgetsType } from "@rjsf/utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { jsonschemaProvider } from "../../providers/jsonschema-provider"; import { jsonschemaProvider } from "../../providers/jsonschema-provider";
import { useForm } from "@refinedev/core"; import { useForm } from "@refinedev/core";
//import TextWidget from "@rjsf/core/src/components/widgets/TextWidget";
import CrudTextWidget from "./widgets/crud-text-widget";
type Props = { type Props = {
schemaName: string, schemaName: string,
@@ -11,6 +14,8 @@ type Props = {
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void //onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
} }
const customWidgets: RegistryWidgetsType = { TextWidget: CrudTextWidget };
export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => { export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
const { onFinish, query, formLoading } = useForm({ const { onFinish, query, formLoading } = useForm({
resource: resource, resource: resource,
@@ -47,6 +52,7 @@ export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
onSubmit={(e) => onFinish(e.formData)} onSubmit={(e) => onFinish(e.formData)}
validator={validator} validator={validator}
omitExtraData={true} omitExtraData={true}
widgets={customWidgets}
/> />
) )
} }

View File

@@ -0,0 +1,15 @@
import TextWidget from "@rjsf/core/lib/components/widgets/TextWidget";
import {FormContextType, getTemplate, RJSFSchema, StrictRJSFSchema, WidgetProps} from "@rjsf/utils";
import {ForeignKeyWidget} from "./foreign-key";
export default function CrudTextWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F>
) {
if (props.schema.hasOwnProperty("foreign_key")) {
return (<ForeignKeyWidget {...props} />);
}else {
return (<CrudTextWidget {...props} />);
}
}

View File

@@ -0,0 +1,60 @@
import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import {Autocomplete, AutocompleteRenderInputParams, debounce} from "@mui/material";
import {useAutocomplete} from "@refinedev/mui";
import { Controller } from "react-hook-form";
import {useState, useEffect, useCallback} from "react";
import TextField from "@mui/material/TextField";
import {axiosInstance} from "@refinedev/simple-rest";
import {useList} from "@refinedev/core";
export const ForeignKeyWidget = (props: WidgetProps) => {
const [inputValue, setInputValue] = useState("");
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const resource = props.schema.foreign_key.reference.resource
const { data, isLoading } = useList({
resource: resource,
pagination: { current: 1, pageSize: 10 },
sorters: [{ field: "name", order: "asc" }],
filters: [{ field: "name", operator: "contains", value: "input" }],
});
const options = data?.data || [];
// const fetchOptions = async (input: string) => {
// try {
//
// } catch (error) {
// console.error("Error fetching options:", error);
// }
// };
// // Debounced version of the fetch function
// const debouncedFetch = useCallback(debounce(fetchOptions, 300), []);
// // Trigger fetch whenever the inputValue changes
// useEffect(() => {
// if (inputValue) {
// debouncedFetch(inputValue);
// } else {
// setOptions([]); // Clear options when input is empty
// }
// }, [inputValue, debouncedFetch]);
return (
<Autocomplete
value={selectedValue}
onChange={(event, newValue) => setSelectedValue(String(newValue))}
inputValue={inputValue}
onInputChange={(event, newInputValue) => setInputValue(newInputValue)}
options={options}
getOptionLabel={(option) => option.name}
loading={isLoading}
renderInput={(params) => (
<TextField {...params} label="Search" variant="outlined" />
)}
/>
);
};