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:
Jakub Zych
2026-10-02 18:37:13 +02:00
parent 0b4ef9c311
commit 48a5b8045a
23 changed files with 1621 additions and 100 deletions

View File

@@ -81,18 +81,53 @@ type relationMessageKeys struct {
UnlinkConfirm string `yaml:"unlinkConfirm"`
Unlinked string `yaml:"unlinked"`
Empty string `yaml:"empty"`
// The child create, update and delete copy and the pivot copy (12.2).
Create string `yaml:"create"`
CreateTitle string `yaml:"createTitle"`
UpdateTitle string `yaml:"updateTitle"`
PreviewTitle string `yaml:"previewTitle"`
Created string `yaml:"created"`
Updated string `yaml:"updated"`
DeleteSelected string `yaml:"deleteSelected"`
DeleteConfirm string `yaml:"deleteConfirm"`
DeleteOneConfirm string `yaml:"deleteOneConfirm"`
Deleted string `yaml:"deleted"`
PivotTitle string `yaml:"pivotTitle"`
PivotSaved string `yaml:"pivotSaved"`
EditPivot string `yaml:"editPivot"`
CreateSubmit string `yaml:"createSubmit"`
UpdateSubmit string `yaml:"updateSubmit"`
PivotSubmit string `yaml:"pivotSubmit"`
LinkSubmit string `yaml:"linkSubmit"`
}
// RelationMessages is a relation manager's copy, every key resolved.
type RelationMessages struct {
Link MessageForms `json:"link"`
LinkHint MessageForms `json:"linkHint"`
CandidateSearch MessageForms `json:"candidateSearch"`
Linked MessageForms `json:"linked"`
UnlinkSelected MessageForms `json:"unlinkSelected"`
UnlinkConfirm MessageForms `json:"unlinkConfirm"`
Unlinked MessageForms `json:"unlinked"`
Empty MessageForms `json:"empty"`
Link MessageForms `json:"link"`
LinkHint MessageForms `json:"linkHint"`
CandidateSearch MessageForms `json:"candidateSearch"`
Linked MessageForms `json:"linked"`
UnlinkSelected MessageForms `json:"unlinkSelected"`
UnlinkConfirm MessageForms `json:"unlinkConfirm"`
Unlinked MessageForms `json:"unlinked"`
Empty MessageForms `json:"empty"`
Create MessageForms `json:"create"`
CreateTitle MessageForms `json:"createTitle"`
UpdateTitle MessageForms `json:"updateTitle"`
PreviewTitle MessageForms `json:"previewTitle"`
Created MessageForms `json:"created"`
Updated MessageForms `json:"updated"`
DeleteSelected MessageForms `json:"deleteSelected"`
DeleteConfirm MessageForms `json:"deleteConfirm"`
DeleteOneConfirm MessageForms `json:"deleteOneConfirm"`
Deleted MessageForms `json:"deleted"`
PivotTitle MessageForms `json:"pivotTitle"`
PivotSaved MessageForms `json:"pivotSaved"`
EditPivot MessageForms `json:"editPivot"`
CreateSubmit MessageForms `json:"createSubmit"`
UpdateSubmit MessageForms `json:"updateSubmit"`
PivotSubmit MessageForms `json:"pivotSubmit"`
LinkSubmit MessageForms `json:"linkSubmit"`
}
// Framework defaults (D-13): every omitted key falls back to backend::lang.
@@ -117,14 +152,31 @@ var (
Deleted: "backend::lang.messages.form.deleted",
}
relationMessageDefaults = relationMessageKeys{
Link: "backend::lang.messages.relation.link",
LinkHint: "backend::lang.messages.relation.link_hint",
CandidateSearch: "backend::lang.messages.relation.candidate_search",
Linked: "backend::lang.messages.relation.linked",
UnlinkSelected: "backend::lang.messages.relation.unlink_selected",
UnlinkConfirm: "backend::lang.messages.relation.unlink_confirm",
Unlinked: "backend::lang.messages.relation.unlinked",
Empty: "backend::lang.messages.relation.empty",
Link: "backend::lang.messages.relation.link",
LinkHint: "backend::lang.messages.relation.link_hint",
CandidateSearch: "backend::lang.messages.relation.candidate_search",
Linked: "backend::lang.messages.relation.linked",
UnlinkSelected: "backend::lang.messages.relation.unlink_selected",
UnlinkConfirm: "backend::lang.messages.relation.unlink_confirm",
Unlinked: "backend::lang.messages.relation.unlinked",
Empty: "backend::lang.messages.relation.empty",
Create: "backend::lang.messages.relation.create",
CreateTitle: "backend::lang.messages.relation.create_title",
UpdateTitle: "backend::lang.messages.relation.update_title",
PreviewTitle: "backend::lang.messages.relation.preview_title",
Created: "backend::lang.messages.relation.created",
Updated: "backend::lang.messages.relation.updated",
DeleteSelected: "backend::lang.messages.relation.delete_selected",
DeleteConfirm: "backend::lang.messages.relation.delete_confirm",
DeleteOneConfirm: "backend::lang.messages.relation.delete_one_confirm",
Deleted: "backend::lang.messages.relation.deleted",
PivotTitle: "backend::lang.messages.relation.pivot_title",
PivotSaved: "backend::lang.messages.relation.pivot_saved",
EditPivot: "backend::lang.messages.relation.edit_pivot",
CreateSubmit: "backend::lang.messages.relation.create_submit",
UpdateSubmit: "backend::lang.messages.relation.update_submit",
PivotSubmit: "backend::lang.messages.relation.pivot_submit",
LinkSubmit: "backend::lang.messages.relation.link_submit",
}
)