Backend improvements: views, fields_get, session, RPC stubs

- Improved auto-generated list/form/search views with priority fields,
  two-column form layout, statusbar widget, notebook for O2M fields
- Enhanced fields_get with currency_field, compute, related metadata
- Fixed session handling: handleSessionInfo/handleSessionCheck use real
  session from cookie instead of hardcoded values
- Added read_progress_bar and activity_format RPC stubs
- Improved bootstrap translations with lang_parameters
- Added "contacts" to session modules list

Server starts successfully: 14 modules, 93 models, 378 XML templates,
503 JS modules transpiled — all from local frontend/ directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-03-31 23:16:26 +02:00
parent 8741282322
commit 9c444061fd
32 changed files with 3416 additions and 148 deletions

View File

@@ -108,54 +108,197 @@ func generateDefaultView(modelName, viewType string) string {
}
func generateDefaultListView(m *orm.Model) string {
// Prioritize important fields first
priority := []string{"name", "display_name", "state", "partner_id", "date_order", "date",
"amount_total", "amount_untaxed", "email", "phone", "company_id", "user_id",
"product_id", "quantity", "price_unit", "price_subtotal"}
var fields []string
count := 0
added := make(map[string]bool)
// Add priority fields first
for _, pf := range priority {
f := m.GetField(pf)
if f != nil && f.IsStored() && f.Type != orm.TypeBinary {
fields = append(fields, fmt.Sprintf(`<field name="%s"/>`, pf))
added[pf] = true
}
}
// Fill remaining slots
for _, f := range m.Fields() {
if f.Name == "id" || !f.IsStored() || f.Name == "create_uid" || f.Name == "write_uid" ||
f.Name == "create_date" || f.Name == "write_date" || f.Type == orm.TypeBinary {
if len(fields) >= 10 {
break
}
if added[f.Name] || f.Name == "id" || !f.IsStored() ||
f.Name == "create_uid" || f.Name == "write_uid" ||
f.Name == "create_date" || f.Name == "write_date" ||
f.Type == orm.TypeBinary || f.Type == orm.TypeText || f.Type == orm.TypeHTML {
continue
}
fields = append(fields, fmt.Sprintf(`<field name="%s"/>`, f.Name))
count++
if count >= 8 {
break
}
added[f.Name] = true
}
return fmt.Sprintf("<list>\n %s\n</list>", strings.Join(fields, "\n "))
}
func generateDefaultFormView(m *orm.Model) string {
var fields []string
for _, f := range m.Fields() {
if f.Name == "id" || f.Name == "create_uid" || f.Name == "write_uid" ||
f.Name == "create_date" || f.Name == "write_date" || f.Type == orm.TypeBinary {
skip := map[string]bool{
"id": true, "create_uid": true, "write_uid": true,
"create_date": true, "write_date": true,
}
// Header with state widget if state field exists
var header string
if f := m.GetField("state"); f != nil && f.Type == orm.TypeSelection {
header = ` <header>
<field name="state" widget="statusbar"/>
</header>
`
}
// Title field (name or display_name)
var title string
if f := m.GetField("name"); f != nil {
title = ` <div class="oe_title">
<h1><field name="name" placeholder="Name..."/></h1>
</div>
`
skip["name"] = true
}
// Split fields into left/right groups
var leftFields, rightFields []string
var o2mFields []string
count := 0
// Prioritize important fields
priority := []string{"partner_id", "date_order", "date", "company_id", "currency_id",
"user_id", "journal_id", "product_id", "email", "phone"}
for _, pf := range priority {
f := m.GetField(pf)
if f == nil || skip[pf] || f.Type == orm.TypeBinary {
continue
}
if f.Type == orm.TypeOne2many || f.Type == orm.TypeMany2many {
continue // Skip relational fields in default form
skip[pf] = true
line := fmt.Sprintf(` <field name="%s"/>`, pf)
if count%2 == 0 {
leftFields = append(leftFields, line)
} else {
rightFields = append(rightFields, line)
}
fields = append(fields, fmt.Sprintf(` <field name="%s"/>`, f.Name))
if len(fields) >= 20 {
count++
}
// Add remaining stored fields
for _, f := range m.Fields() {
if skip[f.Name] || !f.IsStored() || f.Type == orm.TypeBinary {
continue
}
if f.Type == orm.TypeOne2many {
o2mFields = append(o2mFields, fmt.Sprintf(` <field name="%s"/>`, f.Name))
continue
}
if f.Type == orm.TypeMany2many {
continue
}
line := fmt.Sprintf(` <field name="%s"/>`, f.Name)
if len(leftFields) <= len(rightFields) {
leftFields = append(leftFields, line)
} else {
rightFields = append(rightFields, line)
}
if len(leftFields)+len(rightFields) >= 20 {
break
}
}
return fmt.Sprintf("<form>\n <sheet>\n <group>\n%s\n </group>\n </sheet>\n</form>",
strings.Join(fields, "\n"))
// Build form
var buf strings.Builder
buf.WriteString("<form>\n")
buf.WriteString(header)
buf.WriteString(" <sheet>\n")
buf.WriteString(title)
buf.WriteString(" <group>\n")
buf.WriteString(" <group>\n")
buf.WriteString(strings.Join(leftFields, "\n"))
buf.WriteString("\n </group>\n")
buf.WriteString(" <group>\n")
buf.WriteString(strings.Join(rightFields, "\n"))
buf.WriteString("\n </group>\n")
buf.WriteString(" </group>\n")
// O2M fields in notebook
if len(o2mFields) > 0 {
buf.WriteString(" <notebook>\n")
buf.WriteString(" <page string=\"Lines\">\n")
buf.WriteString(strings.Join(o2mFields, "\n"))
buf.WriteString("\n </page>\n")
buf.WriteString(" </notebook>\n")
}
buf.WriteString(" </sheet>\n")
buf.WriteString("</form>")
return buf.String()
}
func generateDefaultSearchView(m *orm.Model) string {
var fields []string
// Add name field if it exists
if f := m.GetField("name"); f != nil {
fields = append(fields, `<field name="name"/>`)
}
if f := m.GetField("email"); f != nil {
fields = append(fields, `<field name="email"/>`)
var filters []string
// Search fields
searchable := []string{"name", "display_name", "email", "phone", "ref",
"partner_id", "company_id", "user_id", "state", "date_order", "date"}
for _, sf := range searchable {
if f := m.GetField(sf); f != nil {
fields = append(fields, fmt.Sprintf(`<field name="%s"/>`, sf))
}
}
if len(fields) == 0 {
fields = append(fields, `<field name="id"/>`)
}
return fmt.Sprintf("<search>\n %s\n</search>", strings.Join(fields, "\n "))
// Auto-generate filter for state field
if f := m.GetField("state"); f != nil && f.Type == orm.TypeSelection {
for _, sel := range f.Selection {
filters = append(filters, fmt.Sprintf(
`<filter string="%s" name="filter_%s" domain="[('state','=','%s')]"/>`,
sel.Label, sel.Value, sel.Value))
}
}
// Group-by for common fields
var groupby []string
groupable := []string{"partner_id", "state", "company_id", "user_id", "stage_id"}
for _, gf := range groupable {
if f := m.GetField(gf); f != nil {
groupby = append(groupby, fmt.Sprintf(`<filter string="%s" name="groupby_%s" context="{'group_by': '%s'}"/>`,
f.String, gf, gf))
}
}
var buf strings.Builder
buf.WriteString("<search>\n")
for _, f := range fields {
buf.WriteString(" " + f + "\n")
}
if len(filters) > 0 {
buf.WriteString(" <separator/>\n")
for _, f := range filters {
buf.WriteString(" " + f + "\n")
}
}
if len(groupby) > 0 {
buf.WriteString(" <group expand=\"0\" string=\"Group By\">\n")
for _, g := range groupby {
buf.WriteString(" " + g + "\n")
}
buf.WriteString(" </group>\n")
}
buf.WriteString("</search>")
return buf.String()
}
func generateDefaultKanbanView(m *orm.Model) string {