How to automatically update the client interface after a Wizard

We want to correctly implement Tryton - Wizard options, where every change made in the Wizard, at the end of the Wizard, is presented in every open interface by the User who made the change.

The context:

We have a menu to manage records, records start with a “status 1” by default.

To facilitate the management of records by status, submenus are implemented, therefore, When the User applies double click on a submenu #1, records with status #1 appear in a tree view. likewise, for all other submenus.

Image: Interface Menu and submenus.

To manage statuses, double-clicking on each record displays the form view assigned to the status, and in the form, the User has selection fields and text fields and buttons. The buttons are used to change the status.

Image: Form view interface.

Each button triggers a Wizard that prompts the User to select the reason for the status change, and enter text with observations of the status change. The Wizard ends by changing to the new status and saving the information.

Image: Wizard window interface.

The difficulty:

Immediately the Wizard finishes, the information entered by the User is saved in the database, but in the current User’s interface the changes are not visible.

To force to see the changes, the User has to switch to the tree view and then click on the “update” button in the Tryton toolbar.

Expected:

To avoid errors or to streamline the workflow, it is required that at the end of each Wizard:

  • Automatically update all changes in the User interface.

or alternatively:

  • The form view is changed to tree view, showing the new information.

Environment:

  • OS: Ubuntu 20.04.1 LTS
  • Python: 3.8.5
  • Trytond: 5.8.1
  • Tryton: 5.8.1
  • Module: 5.8
  • Postgresql: 12.5

Example code:

We have the following code of a Wizard that allows to make changes in the registry, at the end, it calls ‘reload’ to refresh the User interface, without success.

Trying to test another alternative, the code calls the other window action with the new state that will have the registry, we also failed to open that window action.

start_state = 'parameters'
   parameters = StateView(
       'records.manage_records.parameters',
       'records.manage_records_parameters_view_form', [
           Button('Cancelar', 'end', 'tryton-cancel'),
           Button('Guardar', 'save', 'tryton-forward', default=True)
       ])
   save = StateTransition()
   open_records = StateAction('records.act_state_closed_list')

   def default_parameters(self, name):
       return {
           'closing_date': datetime.date.today(),
       }

   def transition_save(self):
       pool = Pool()
       Record = pool.get('records')
       records = Record.browse(Transaction().context['active_ids'])
       for record in records:
           record.closing_date = self.parameters.closing_date
           record.solution_summary = self.parameters.solution_summary
           record.cause_incidence = self.parameters.cause_record
           record.sub_state = 'new_closed'
       Record.save(records)
       # Option 1
       return 'end'
       # Option 2
       return 'open_records'
  
   def do_open_record(self):
       return 'end'

   # the last step ...
   def end(self):
       return 'reload'

We appreciate your help !

I guess what you want is to remove the current record on which the wizard was executed to be removed from the screen. Well that’s not possible in Tryton.
I think you are trying too much to funnel the user actions. This goes against the Tryton design and so you will have a lot of difficulties.
I think you should do like we do in standard module with workflow, use “action window domain” for each status (this way the status will not be hidden by domain inversion), and work to have a single view that works for each status (this is less astonishing for the user).

Thank you @ced,

We want to understand how to develop the module following Tryton’s best practices.

Complementing our Context, in addition to grouping the records by status, (double click on submenus) for each record of a status, the module also classifies it by its reason, “motive#” (tabs within the tree view).

To apply it to an example such as borrowing samples of books in a library.
The states of the exemplar are “available”, “borrowed”, “maintenance” … others.
And each status with its own motives.

An “available” book sample can be for a motive such as: return, acquisition, etc. In our Example Code #2 we simply call them Motive1, Motive2, Motive3… (see below).

The above, as shown in the following image.

So, to group the records by “Status” in submenus we use ir.action.act_window.domain, and to group the Motives in tabs, we are also using ir.action.act_window.domain.

Example Code 2:

Please take a look at our code:

file book.py

class Book(ModelSQL, ModelView):
   'Book'
   __name__ = 'book'
 
   title = fields.Char('Title')
   state = fields.Selection([('available', 'Available'),
                             ('borrowed', 'Borrowed')], 'State')
   motive = fields.Selection([('motive1', 'Motive1'), ('motive2', 'Motive2'),
                              ('motive3', 'Motive3'), ('motive4', 'Motive4')],
                             'Motive')
 
   @staticmethod
   def default_state():
       return 'available'
 
   @staticmethod
   def default_motive():
       return 'motive1'

file book.xml

<tryton>
   <data>
       <record model="ir.ui.view" id="book_available_view_form">
           <field name="model">book</field>
           <field name="type">form</field>
           <field name="name">book_available_form</field>
       </record>
       <record model="ir.ui.view" id="book_available_view_tree">
           <field name="model">book</field>
           <field name="type">tree</field>
           <field name="name">book_available_tree</field>
       </record>
       <record model="ir.action.act_window" id="act_book_available_list">
           <field name="name">Available</field>
           <field name="res_model">book</field>
           <field name="search_value"/>
       </record>
       <record model="ir.action.act_window.view" id="act_book_available_list_view1">
           <field name="sequence" eval="10"/>
           <field name="view" ref="book_available_view_tree"/>
           <field name="act_window" ref="act_book_available_list"/>
       </record>
       <record model="ir.action.act_window.view" id="act_book_available_list_view2">
           <field name="sequence" eval="20"/>
           <field name="view" ref="book_available_view_form"/>
           <field name="act_window" ref="act_book_available_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_available_list_domain_motive1">
           <field name="name">Motive1</field>
           <field name="sequence" eval="10"/>
           <field name="domain" eval="[('motive', '=', 'motive1')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_available_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_available_list_domain_motive2">
           <field name="name">Motive2</field>
           <field name="sequence" eval="20"/>
           <field name="domain" eval="[('motive', '=', 'motive2')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_available_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_available_list_domain_all">
           <field name="name">All</field>
           <field name="sequence" eval="9999"/>
           <field name="domain" eval="[('state', '=', 'available')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_available_list"/>
       </record>
       <menuitem parent="menu_root" action="act_book_available_list" sequence="10" id="menu_available"/>
       <!-- # Book Borrowed # -->
       <record model="ir.ui.view" id="book_borrowed_view_form">
           <field name="model">book</field>
           <field name="type">form</field>
           <field name="name">book_borrowed_form</field>
       </record>
       <record model="ir.ui.view" id="book_borrowed_view_tree">
           <field name="model">book</field>
           <field name="type">tree</field>
           <field name="name">book_borrowed_tree</field>
       </record>
       <record model="ir.action.act_window" id="act_book_borrowed_list">
           <field name="name">Borrowed</field>
           <field name="res_model">book</field>
           <field name="search_value"/>
       </record>
       <record model="ir.action.act_window.view" id="act_book_borrowed_list_view1">
           <field name="sequence" eval="10"/>
           <field name="view" ref="book_borrowed_view_tree"/>
           <field name="act_window" ref="act_book_borrowed_list"/>
       </record>
       <record model="ir.action.act_window.view" id="act_book_borrowed_list_view2">
           <field name="sequence" eval="20"/>
           <field name="view" ref="book_borrowed_view_form"/>
           <field name="act_window" ref="act_book_borrowed_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_borrowed_list_domain_motive3">
           <field name="name">Motive3</field>
           <field name="sequence" eval="10"/>
           <field name="domain" eval="[('motive', '=', 'motive3')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_borrowed_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_borrowed_list_domain_motive4">
           <field name="name">Motive4</field>
           <field name="sequence" eval="20"/>
           <field name="domain" eval="[('motive', '=', 'motive4')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_borrowed_list"/>
       </record>
       <record model="ir.action.act_window.domain" id="act_book_borrowed_list_domain_all">
           <field name="name">All</field>
           <field name="sequence" eval="9999"/>
           <field name="domain" eval="[('state', '=', 'borrowed')]" pyson="1"/>
           <field name="count" eval="True"/>
           <field name="act_window" ref="act_book_borrowed_list"/>
       </record>
       <menuitem parent="menu_root" action="act_book_borrowed_list" sequence="20" id="menu_borrowed"/>
   </data>
</tryton>

file wizard.py

class ChangeMotive(Wizard):
   'Change Motive'
   __name__ = 'book.change_motive'
 
   start_state = 'parameters'
   parameters = StateView(
       'book.change_motive.parameters', 'change_motive_parameters_form', [
           Button('Cancel', 'end', 'tryton-cancel'),
           Button('Change', 'change', 'tryton-next', default=True)
       ])
   change = StateTransition()
  
   def transition_change(self):
       pool = Pool()
       Book = pool.get('book')
       books = Book.browse(Transaction().context['active_ids'])
       for book in books:
           book.state = 'borrowed'
           book.motive = self.parameters.motive
       Book.save(books)
       return 'end'
  
   def end(self):
       return 'reload'
 
 

Code #2 performs as expected, but does not update the user interface. :thinking:
Please let us know how to implement it well, so that it works in harmony with Tryton.

You could have window domains per state and motive for example.
Or you could just use bookmarks to filter a list by motive.

Following your first suggestion, it seems that we are implementing the Window Domains wrong because it generates a tab for each motive in general, but it does not meet our expectations, we want to group by each state previously selected, only the motive that correspond to that state, and display to the user the records of each motive of a state, in tabs:

  • State1 corresponds only to the motives: Motive1, Motive2, Motive3.
  • State2 corresponds to: Motive4, Motive5.
  • State3 corresponds to: Motive6, Motive7, Motive8…
  • State4…

And so on for the other states and the other motives required for the module.

On the other hand, the second suggestion to use Bookmarks, I don’t know it, I can’t find in the Tryton Server documentation, how to implement this bookmarks.

It will be of great help, links with sample code implementing Bookmarks or Window Domains.

Best regards.

As a user you can filter by field you are talking about, click star icon to save the bookmark and later using it through bookmark icon.