From our example, proxy and instance appear to be completely coupled -
even the names of the components and attributes are tied. This is a
precise interpretation of the situation, but in my opinion, this
coupling is not only unavoidable, it is the essence of UI architecture.
Treating the interface as a completely separate entity from the object
it manipulates is, honestly, a bad idea, because the interface is a
representation of the object, and as such, essentially coupled to it.
Would it make sense to remove the url
attribute from the model
and not remove it from the interface in question?
Because the UI is really a representation, however, there are times where the contents of the widget and its attached proxy attribute must differ in some way. Often, it is a matter of cardinality: more than one widget defines a single proxy attribute, or vice-versa; at other times, the data format in the widget does not match the format in the model (think of dates, represented by strings in the interface, but stored as DateTime objects in the object)7. The Kiwi Proxy was designed to cater to these different requirements, using accessor functions when available.
Accessors provide an easy way to translate the model value to the
interface value: a pair of get_*()
and set_*()
functions
implemented in the model that perform internal manipulation of its
variables, removing the need to directly manipulate the instance
variable. You can define accessors for as few or as many model
attributes you want.
To make the process clearer, it is worth discussing how the model and the UI are updated. The heuristics for updating a model are:
X
, it looks at
the model attached to it.
set_X()
method, it is called, with
the new value as its only parameter.
setattr()
, which is the equivalent of model.X = value
. If
Proxies.set_attr_warnings(True)
has been called, a warning like
the following will be printed:
Kiwi warning: could not find method set_title in model <__main__.NewsItem instance at 0x82011ac>, using setattr()
The heuristics for updating the interface widget are:
X
.
get_X()
(which should return a single value). If the accessor does not exist, it
will attempt to access the model's variable directly (using
getattr()
). As with setattr()
above, a warning will be
printed if attribute warnings are enabled.
X
is altered (item.X =
"foo"
), a notification is sent to the proxy, with the attribute that
was altered. If a callback calls self.update("X")
, a notification
is sent to the proxy, with the attribute that was altered.
X
from the model using the same method as in step 2, and updates the
widget contents accordingly.
Summarizing: if you would like to customize the connection between model
and proxy for an attribute, implement accessor functions
(get_foo()
and set_foo()
) for it. If you would like to
verify that no direct instance manipulations are happening, use the
module function set_attr_warnings()
and check the output
printed to the console's standard error.
Let's extend our previous example to provide an accessor and explain how things work out (newsform3.py).
class NewsItem(FrameWork.Model): """An instance representing an item of news. Attributes: title, author, url""" def set_url(self, url): """sets the url, prefixing "http://" if missing""" http = "http://" if len(url) > len(http) and string.find(url, http) != 0: url = http + url self.url = url
In this example, we provide an accessor for setting the url (a "setter")
prefixed by "http://". The accessor is called when the entry's text
changes (for each character inserted or deleted), which is why I have to
check for the length of the url typed in. Note that I don't provide a
get_url()
method (a "getter"), which means the raw url would be
loaded from the instance into the interface. In this specific case, this
is not an issue, because data is only loaded from the instance at
instantiation time and when the model attribute is changed. However, for
most cases both setter and getter need to convert to and from the model
format.