Skip to content

Counted buttons

On a customer record you want to see “12 orders” and click through. The display is not the problem: the module that owns the customer record must know nothing about the sales module. A direct import between business modules is forbidden (rule 8), for a good reason: the day a customer uninstalls sales, the customer record must not break.

The consuming module declares the link on the other model, and neither knows the other.

from kewos.smart_buttons import has_smart_button, register_smart_button
def register(registry):
...
if not has_smart_button("kws_partner", "invoices"): # boot re-imports
register_smart_button(
"kws_partner", # the record that will carry the button (core)
"invoices", # its key, unique on that model
label_key="account.smartbutton.partner_moves",
target_model="account_move", # what is counted (account module)
filter_field="partner_id", # the field pointing back at the record
icon="receipt",
)

The account module writes those lines. core, which owns kws_partner, never mentions account. Remove account and the button goes; the partner record does not move.

What the server returns

One generic endpoint, for any model:

GET /api/v1/library_title/1/smart-buttons
[{"key":"copies","label_key":"library.smartbutton.copies","icon":"book",
"count":2,"target_model":"library_copy","filter_field":"title_id",
"filter_value":1,"target_module":"library"}]

The count is real (select count(*) from library_copy where title_id = 1 does return 2), and the frontend needs to know nothing specific: with target_module, target_model, filter_field and filter_value it can render the button and where the click leads. No screen is hardcoded (rule 48).

The count is not a naive count(*)

It runs inside the filtered session, with record rules and the caller’s read permission. Two consequences worth holding on to:

  • two users can see two different numbers on the same record, and that is correct: each counts what they may see. A salesperson who only sees their own orders reads their own total;
  • the number does not leak what the list would hide. A global count would reveal that invisible records exist, which is an information leak even without showing their content.

The record itself is loaded through the filtered session before any counting: asking for the buttons of a record you may not read answers 404, not 403. Not being allowed and not existing look alike, deliberately.

GET /api/v1/library_title/9999/smart-buttons -> 404

The groups field

register_smart_button(..., groups=["accounting_auditor"])

It hides the button from everyone else. It is not a security control: it is screen tidying. The security is the filtered count and the 404 above, which hold even when somebody calls the endpoint by hand.