feat(12.2-03): add hasMany relation contracts, relation forms and child create
- RelationContract gains Kind (empty is belongsToMany) and ForeignKey, with kind-aware boot checks
- manage.form, view.form and pivot.form compile against the related or pivot model; $/ paths resolve inside the plugin
- view toolbarButtons accept create|update|delete|link|unlink, each the capability of its routes
- POST .../relations/{name}/records creates a child through the manage form; the server sets the hasMany key
- relation schema carries kind, deferrable and the localized forms; 17 new relation message keys in en and pl
This commit is contained in:
@@ -304,34 +304,47 @@ func decodeFields(raw []byte) ([]FormField, error) {
|
||||
}
|
||||
|
||||
func (m *fieldMap) UnmarshalYAML(node ast.Node) error {
|
||||
items, err := decodeFieldMapping(node, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.items = items
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeFieldMapping compiles a fields mapping in source order. rename, when
|
||||
// set, maps a raw field name (such as WinterCMS's pivot[role]) to the name
|
||||
// that must be an identifier.
|
||||
func decodeFieldMapping(node ast.Node, rename func(string) string) ([]FormField, error) {
|
||||
node = unwrapNode(node)
|
||||
if _, ok := node.(*ast.NullNode); ok || node == nil {
|
||||
m.items = []FormField{}
|
||||
return nil
|
||||
return []FormField{}, nil
|
||||
}
|
||||
mapping, ok := node.(*ast.MappingNode)
|
||||
if !ok {
|
||||
return fmt.Errorf("fields must be a mapping")
|
||||
return nil, fmt.Errorf("fields must be a mapping")
|
||||
}
|
||||
items := make([]FormField, 0, len(mapping.Values))
|
||||
seen := map[string]struct{}{}
|
||||
for _, entry := range mapping.Values {
|
||||
name, err := nodeString(unwrapNode(entry.Key))
|
||||
if err == nil && rename != nil {
|
||||
name = rename(name)
|
||||
}
|
||||
if err != nil || !identifier(name) {
|
||||
return fmt.Errorf("field name %q is not an identifier", nodeText(entry.Key))
|
||||
return nil, fmt.Errorf("field name %q is not an identifier", nodeText(entry.Key))
|
||||
}
|
||||
if _, dup := seen[name]; dup {
|
||||
return fmt.Errorf("duplicate field %s", name)
|
||||
return nil, fmt.Errorf("duplicate field %s", name)
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
field, err := compileFieldNode(name, unwrapNode(entry.Value))
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %s: %w", name, err)
|
||||
return nil, fmt.Errorf("field %s: %w", name, err)
|
||||
}
|
||||
items = append(items, field)
|
||||
}
|
||||
m.items = items
|
||||
return nil
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func compileFieldNode(name string, node ast.Node) (FormField, error) {
|
||||
|
||||
Reference in New Issue
Block a user