Skip to content

Commit

Permalink
fix natural key handling; add foreign key links
Browse files Browse the repository at this point in the history
  • Loading branch information
sheppard committed Feb 9, 2024
1 parent 8849220 commit f08d7fd
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 41 deletions.
19 changes: 14 additions & 5 deletions packages/app/src/app.js
@@ -1,5 +1,5 @@
import ds from "@wq/store";
import orm from "@wq/model";
import orm, { getRootFields } from "@wq/model";
import outbox from "@wq/outbox";
import router from "@wq/router";
import spinner from "./spinner.js";
Expand Down Expand Up @@ -504,8 +504,7 @@ const syncUpdateUrl = {

// Return a list of all foreign key fields
app.getParents = function (page) {
var conf = _getConf(page);
return conf.form
return getRootFields(_getConf(page))
.filter(function (field) {
return field["wq:ForeignKey"];
})
Expand Down Expand Up @@ -718,9 +717,19 @@ async function _displayList(ctx, parentInfo) {
}
}
if (parentInfo) {
conf.form.forEach(function (field) {
getRootFields(conf).forEach(function (field) {
if (field["wq:ForeignKey"] == parentInfo.parent_page) {
filter[field.name + "_id"] = parentInfo.parent_id;
const naturalKey = field.name.match(
/^([^\]]+)\[([^\]]+)\]$/
);
if (naturalKey) {
filter[naturalKey.slice(1).join("__")] =
parentInfo.parent_id;
} else if (field.type === "select") {
filter[field.name + "_id"] = [parentInfo.parent_id];
} else {
filter[field.name + "_id"] = parentInfo.parent_id;
}
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/auth.js
Expand Up @@ -133,7 +133,7 @@ export default {
wqPageConf =
(config && config.pages && config.pages[pageConf.name]) || {};
return {
user,
user: ctx.user_id ? ctx.user : user,
is_authenticated: !!user,
app_config: this.app.config,
user_config: config,
Expand Down
7 changes: 5 additions & 2 deletions packages/material-web/src/components/Header.js
Expand Up @@ -6,7 +6,8 @@ import { useMinWidth } from "../hooks.js";
export default function Header() {
const title = useSiteTitle(),
links = useBreadcrumbs(),
{ Logo, Breadcrumbs, IconButton, NavMenuPopup } = useComponents(),
{ Logo, SiteTitle, Breadcrumbs, IconButton, NavMenuPopup } =
useComponents(),
fixedMenu = useMinWidth(600),
[open, setOpen] = useState(false);
return (
Expand All @@ -24,7 +25,9 @@ export default function Header() {
edge="start"
/>
)}
<Typography variant="h6">{title}</Typography>
<Typography variant="h6">
<SiteTitle title={title} />
</Typography>
</Toolbar>
</AppBar>
<Breadcrumbs links={links} />
Expand Down
4 changes: 2 additions & 2 deletions packages/model/src/index.js
@@ -1,4 +1,4 @@
import { Model, model as createModel } from "./model.js";
import { Model, model as createModel, getRootFields } from "./model.js";

const orm = {
// Plugin attributes
Expand Down Expand Up @@ -50,4 +50,4 @@ const orm = {

export default orm;

export { Model, createModel, createModel as model };
export { Model, createModel, createModel as model, getRootFields };
36 changes: 33 additions & 3 deletions packages/model/src/model.js
Expand Up @@ -594,9 +594,20 @@ class Model {
filterFields() {
let fields = [this.idCol];
fields = fields.concat(
(this.config.form || []).map((field) =>
field["wq:ForeignKey"] ? `${field.name}_id` : field.name
)
getRootFields(this.config).map((field) => {
if (field["wq:ForeignKey"]) {
const naturalKey = field.name.match(
/^([^\]]+)\[([^\]]+)\]$/
);
if (naturalKey) {
return naturalKey.slice(1).join("__");
} else {
return `${field.name}_id`;
}
} else {
return field.name;
}
})
);
fields = fields.concat(Object.keys(this.functions));
fields = fields.concat(this.config.filter_fields || []);
Expand Down Expand Up @@ -793,6 +804,7 @@ class Model {
this.functions[attr] ||
isPotentialBoolean(comp) ||
isPotentialNumber(comp) ||
isPotentialNaturalKey(attr) ||
Array.isArray(comp)
);
}
Expand All @@ -802,6 +814,13 @@ class Model {

if (this.functions[attr]) {
value = this.compute(attr, item);
} else if (attr.includes("__")) {
const parts = attr.split("__");
if (parts.length === 2 && item[parts[0]]) {
value = item[parts[0]][parts[1]];
} else {
value = item[attr];
}
} else {
value = item[attr];
}
Expand Down Expand Up @@ -850,4 +869,15 @@ function isPotentialNumber(value) {
return typeof value !== "number" && !Number.isNaN(+value);
}

function isPotentialNaturalKey(attr) {
return typeof attr == "string" && attr.includes("__");
}

export function getRootFields(conf) {
const root = (conf.form || []).find(
(field) => field.name === "" && field.type === "group"
);
return (conf.form || []).concat((root && root.children) || []);
}

export { model, Model };
16 changes: 13 additions & 3 deletions packages/react/src/components/AutoForm.js
Expand Up @@ -96,9 +96,19 @@ export function initData(form, data) {
}

form.forEach((field) => {
const fieldName = field["wq:ForeignKey"]
? `${field.name}_id`
: field.name;
let fieldName = field.name;
if (field["wq:ForeignKey"]) {
const naturalKey = field.name.match(/^([^\]]+)\[([^\]]+)\]$/);
if (
naturalKey &&
data[naturalKey[1]] &&
data[naturalKey[1]][naturalKey[2]]
) {
fieldName = naturalKey[1];
} else {
fieldName = `${field.name}_id`;
}
}

let value;
if (field.type === "repeat") {
Expand Down
7 changes: 6 additions & 1 deletion packages/react/src/components/AutoInput.js
Expand Up @@ -16,7 +16,12 @@ export default function AutoInput({ name, choices, type, bind = {}, ...rest }) {
let inputType,
required = bind.required;
if (rest["wq:ForeignKey"]) {
name = `${name}_id`;
const naturalKey = name.match(/^([^\]]+)\[([^\]]+)\]$/);
if (naturalKey) {
name = naturalKey.slice(1).join(".");
} else {
name = `${name}_id`;
}
inputType = "foreign-key";
} else if (type === "select1" || type === "select one") {
if (!choices) {
Expand Down
21 changes: 21 additions & 0 deletions packages/react/src/components/ForeignKeyLink.js
@@ -0,0 +1,21 @@
import React from "react";
import { useComponents, useModel, useReverse } from "../hooks.js";
import PropTypes from "prop-types";

export default function ForeignKeyLink({ id, label, model }) {
const { Link } = useComponents(),
reverse = useReverse(),
obj = useModel(model, id || -1) || { label: id };
if (!id) {
return null;
}
return (
<Link to={reverse(`${model}_detail`, id)}>{label || obj.label}</Link>
);
}

ForeignKeyLink.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
model: PropTypes.string,
};
26 changes: 26 additions & 0 deletions packages/react/src/components/ManyToManyLink.js
@@ -0,0 +1,26 @@
import React from "react";
import { useComponents } from "../hooks.js";
import PropTypes from "prop-types";

export default function ManyToManyLink({ ids, labels, model }) {
const { ForeignKeyLink, Text } = useComponents();
if (labels && typeof labels === "string") {
labels = [labels];
}
return (ids || []).map((id, index) => (
<React.Fragment key={id}>
<ForeignKeyLink
id={id}
label={(labels || [])[index]}
model={model}
/>
<Text> </Text>
</React.Fragment>
));
}

ManyToManyLink.propTypes = {
ids: PropTypes.arrayOf(PropTypes.string),
labels: PropTypes.arrayOf(PropTypes.string),
model: PropTypes.string,
};
108 changes: 87 additions & 21 deletions packages/react/src/components/PropertyTable.js
Expand Up @@ -3,7 +3,13 @@ import { useComponents } from "../hooks.js";
import PropTypes from "prop-types";

const Value = ({ values, field }) => {
const { FormatJson, ImagePreview, FileLink } = useComponents(),
const {
FormatJson,
ImagePreview,
FileLink,
ForeignKeyLink,
ManyToManyLink,
} = useComponents(),
value = values[field.name];

if (field.children && values[field.name]) {
Expand All @@ -23,15 +29,21 @@ const Value = ({ values, field }) => {
);
}
} else if (field["wq:ForeignKey"]) {
if (value && typeof value === "object" && value.label) {
return value.label;
} else {
return (
values[field.name + "_label"] ||
values[field.name + "_id"] ||
value + ""
);
}
const id = getForeignKeyId(values, field),
label = getForeignKeyLabel(values, field);
return field["type"] === "select" ? (
<ManyToManyLink
ids={id}
labels={label}
model={field["wq:ForeignKey"]}
/>
) : (
<ForeignKeyLink
id={id}
label={label}
model={field["wq:ForeignKey"]}
/>
);
} else if (field.choices) {
const choice = field.choices.find((c) => c.name === value);
if (choice && choice.label) {
Expand Down Expand Up @@ -59,6 +71,42 @@ const isInteractive = (values, field) => {
}
};

const getForeignKeyId = (values, field) => {
const naturalKey = field.name.match(/^([^\]]+)\[([^\]]+)\]$/);
if (naturalKey) {
return (values[naturalKey[1]] || {})[naturalKey[2]] || "";
}
const value = values[field.name];
if (value && typeof value === "object" && value.id) {
return value.id;
} else if (values[field.name + "_id"]) {
return values[field.name + "_id"];
} else {
return (value || "") + "";
}
};

const getForeignKeyLabel = (values, field) => {
const value = values[field.name];
if (value && typeof value === "object" && value.label) {
return value.label;
} else if (values[field.name + "_label"]) {
return values[field.name + "_label"];
} else {
return null;
}
};

const showInTable = (field) => {
if (field.show_in_table === false) {
return false;
}
if (field.type && field.type.startsWith("geo")) {
return field.show_in_table || false;
}
return true;
};

export default function PropertyTable({ form, values }) {
const { Table, TableBody, TableRow, TableCell } = useComponents(),
rootFieldset = form.find(
Expand All @@ -72,7 +120,7 @@ export default function PropertyTable({ form, values }) {
return (
<Table>
<TableBody>
{fields.map((field) => (
{fields.filter(showInTable).map((field) => (
<TableRow key={field.name}>
<TableCell>{field.label || field.name}</TableCell>
<TableCell interactive={isInteractive(values, field)}>
Expand All @@ -91,19 +139,37 @@ PropertyTable.propTypes = {
};

function PropertyTableList({ form, values }) {
const { Divider } = useComponents();
if (!Array.isArray(values)) {
const { Table, TableHead, TableBody, TableRow, TableTitle, TableCell } =
useComponents();
if (!Array.isArray(values) || values.length === 0) {
return null;
}
return (
<>
{values.map((vals, i) => (
<React.Fragment key={(vals && vals.id) || i}>
{i > 0 && <Divider />}
<PropertyTable form={form} values={vals} />
</React.Fragment>
))}
</>
<Table>
<TableHead>
<TableRow>
{form.filter(showInTable).map((field) => (
<TableTitle key={field.name}>
{field.label || field.name}
</TableTitle>
))}
</TableRow>
</TableHead>
<TableBody>
{values.map((vals, i) => (
<TableRow key={(vals && vals.id) || i}>
{form.filter(showInTable).map((field) => (
<TableCell
key={field.name}
interactive={isInteractive(values, field)}
>
<Value values={vals} field={field} />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
);
}
PropertyTableList.propTypes = {
Expand Down

0 comments on commit f08d7fd

Please sign in to comment.