Consider the example of a simple foreign exchange calculator app.
I can define my model using traitlets:
from traitlets import HasTraits, Float, observe, Enum
import math
class FXModel(HasTraits):
    domestic_qty = Float()
    foreign_qty = Float()
    fx_rate  = Float(float('nan')) # in units of domestic_qty/foreign_qty
    lock = Enum(['domestic', 'foreign'], default_value='domestic')
    _calculating = Enum([None, 'domestic', 'foreign'], default_value=None)
    def calc_foreign(self):
        if not math.isnan(self.fx_rate):
            self._calculating = 'foreign'
            self.foreign_qty = self.domestic_qty / self.fx_rate
            self._calculating = None
    def calc_domestic(self):
        if not math.isnan(self.fx_rate):
            self._calculating = 'domestic'
            self.domestic_qty = self.foreign_qty * self.fx_rate
            self._calculating = None
    @observe('domestic_qty')
    def on_domestic(self, change):
        if self._calculating is None:
            self.calc_foreign()
    @observe('foreign_qty')
    def on_foreign(self, change):
        if self._calculating is None:
            self.calc_domestic()
    @observe('fx_rate')
    def on_fxrate(self, change):
        if self.lock == 'domestic':
            self.calc_foreign()
        else:
            self.calc_domestic()
And a corresponding simple "print" based view:
class FXView:
    def __init__(self, model):
        self.model = model
    def show(self):
        print("""
        domestic_qty: {:.4g}
        foreign_qty:  {:.4g}
        fx_rate:      {:.4g}
        lock:         {}""".format(
            self.model.domestic_qty,
            self.model.foreign_qty,
            self.model.fx_rate,
            self.model.lock
        ))
Here's how it works:
>> fx_model = FXModel(domestic_qty = 100., fx_rate = 200.)
>> fx_view = FXView(fx_model)
>> fx_view.show()
    domestic_qty: 100
    foreign_qty:  0.5
    fx_rate:      200
    lock:         domestic
>> fx_model.fx_rate = 195.
>> fx_view.show()
    domestic_qty: 100
    foreign_qty:  0.5128
    fx_rate:      195
    lock:         domestic
I have also created a view using ipywidgets:
import ipywidgets as widgets
domestic_label = widgets.Label("Domestic quantity")
domestic_field = widgets.FloatText()
foreign_label = widgets.Label("Foreign quantity")
foreign_field = widgets.FloatText()
fx_label = widgets.Label("Exchange rate (domestic/foreign)")
fx_field = widgets.FloatText()
lock_label = widgets.Label("If rates change, keep ")
lock_field = widgets.Dropdown(options=["domestic", "foreign"])
lock_label_post = widgets.Label('fixed')
ipyview = widgets.HBox([widgets.VBox([domestic_label, foreign_label, fx_label, lock_label]),
              widgets.VBox([domestic_field, foreign_field, fx_field, widgets.HBox([lock_field, lock_label_post])])])
It looks really good:
My question is; how can I "bind" my model and my ipyview together? I have some experience with enaml where this is possible via the operator := and friends.
What's the best way of doing this with ipywidgets?
