This is the base class for SqlMask and SqlTable that implements the common interface.
The first and mandatory argument is the mapper of the object that will be retrieved and displayed. Alternatively any other objects from which the sqlwidget infers them (e.g.: the tablename and the metadata where the table can be autoloaded):
In older releases different options: class_, table, or mapper where used. Now a DeprecationWarning is raised if you use those opts.
Any sqlwidget needs a session as well.
Metadata is also used when auto-loading tables referenced by foreign keys to display a better representation of the referenced record.
Since a typical scenario is to have to provide a session different in each SqlWidget and a metadata, an object is provided -dbproxy- that can be initialized from the engine specification:
from sqlkit.widgets import SqlMask, SqlTable
from sqlkit import dbproxy
db = proxy.DbProxy(engine="sqlite:///model/movies.sqlite")
SqlTable("movies", dbproxy=db)
below you can see some alternatives that would work as well:
Session = sessionmaker(bind=self.metadata.bind, autocommit=False)
sess = Session()
meta = MetaData()
meta.bind = "sqlite:///model/movies.sqlite"
#
SqlTable("movies", session=sess, metadata=meta)
# passing a mapped class (Movie here is build with declarative layer):
# the metadata is found from the mapper.local_table.metadata
SqlTable(Movies, session=sess)
Session are created with autoflush=False, expire_on_commit=False but can be changed when building DbProxy.
Since version 0.8.6 default value for autocommit has been turned to True to prevent idle in transaction in postgresql.
The reason to have expire_on_commit=False is that if you don’t set it, after every commit, you have to reload all objects and the interface turns very slow, especially when working with a remote database.
expire_on_commit is a recent addition to sqlalchemy session (around sa rel. 5.03rc) so I try it and fallback to default that would turn to be slower. Previous 0.5 rel called it autoflush
The main widget used to edit a table or sqlalchemy selectable, i.e. almost everything you can express as table or for which you can provide a mapper or a mapped class.
You won’t use this as such: this is just the parent of SqlTable an SqlMask.
Parameters: |
|
---|
Add Constraints. A constraint may be expressed via keyworks in django-like syntax. Eg.:
name_like='uno%'
genres__name__regexp='sto'
director_id__birth_date__gte='1/1/1970'
if multiple conditions are passed, they will be ANDed unless ‘or_=True’
A constraint may also be build directly into the self.query object.
Parameter: | OR – the condition will be ORed |
---|
Parameter: | active – boolean: make the filter active/inactive |
---|
see Filters and add_constraint above. Note that filter are always ANDed (OR arameter is not available).
If you have a field_name named ‘active’, the active parameter will hide it. Use keyword active__eq to bypass it.
set ‘records’ as the list of record to manage
Parameters: |
|
---|
set_format(format_dict). format_dict is a dictionary whose key is the field_name and the value is the format of the column. Format_dict can have keys that do not correspond to any field in the widget. That makes it possible to reuse a format dict.
The format dict is passed to related widgets as well.
See Localization for more info
Parameter: | format_dict – a dict of field_name/format strings |
---|
Write on the status bar if present in this sqlwidget or in the relationship_leader
Adds a message in the stack of messages of the status bar, and removes it after seconds seconds. If delay=True it uses gobject.idle_add to give more chance to be visible (not hidden by other automatic messages)
Parameters: |
|
---|
return the value from the widget
Parameters: |
|
---|
set the value of any field present in gui_fields. Uses field.set_value if initial is False, run on_change_value
Parameters: |
|
---|
Parameters: |
|
---|
Set mode for this widget. Mode can be a string composed with the following letters that correspond to permissions possibly preceded by + or -.
s: SELECT. The user can view the records already selected (i.e. use Forward/Backward) or set by set_records. This is always granted and as such it’s pointless to set it (or revoke it) i: INSERT. The user can insert new records u: UPDATE. The user can update records d: DELETE. The user can delete records b: browse. The user can use the filter panel
If mode start with + or - the following permissions are granted/revoked for the widget by adding or removing from the modes already present. If no sign is used the mode is set.
mode is a property, you can set it directly: self.mode = 'b'
The mode influences permission by setting menu entries active or not. It’s not acting on the session. If an object has been inserted in the session a simple update operation can let it be inserted. This is by design.
Related table inheritate the same mode but you can programmatically reset it and make it independent from the master using option reset=True.
Implementation
Mode are implemented acting on uimanager/actionsgroup. You may read UiManager: menu and actions
Note
at present it’s not possible to insert a record in a table that is not updatable
Adds an action to a menu and removes it with ‘selection-done’
Parameters: |
|
---|
reload the data from the database taking all filter/constraints into consideration
Parameters: |
|
---|
sql may be a tuple (sql_statement, bind_params) or just a sql_statement
run session.commit() and take care of possible exceptions
Parameter: | message – a message to be written in the status bar (default: Saved) |
---|
keep track of the error in self.validation_errors/validation_warnings so that a further process can collect them and present them to the user
Parameter: | error – the ValidationError or an error message string |
---|---|
Field_name: | the field_name to which the error refers. Defaults to ‘record validation’ |
simple way to add a not nullable field error
Parameter: | field_name – the field_name that cannot be nullable |
---|
a container for all related sqlwidgets (i.e.: SqlWidgets that have a relation with this sqlwidget defined by sqlalchemy and that are displayed in the widget).
This is used in all situation in which you need to fine-tune the configuration of a related table (completion, layout, ...)
the layout definition for this SqlWidget. This is the used definition for SqlMask while for SqlTable is only used if the record is opened in SqlMask (right click on the record in SqlTable).
You can also set it on a related table:
GUI="director @ m2m=movies"
t = SqlMask(Director, layout=GUI, ...)
t.related.movies.layout = "title @ m2m=actors"
noup can be a set of field_name or a comma separated string with possible +- sign to add/remove field_names to the set of field_names that will not be possible to update
Note that to add a non editable field_name you must used ‘+field_name’. Using simply ‘field_name’ will reset the list to only that field_name
record-selected: | |||
---|---|---|---|
A record has been displayed in Mask or selected in Table. The callback will just receive the widget as argument.
|
|||
record-saved: | A record has been saved. This signal is not emitted from within session extension. That means you are sure there will be just one signal for each button press on “save” button. This is issued from within the SqlWidget.commit() method independently from the fact that a real modification occurred, so you are not guaranteed any modification took place. Was originally added to implement a destroy of the widget when the save operation was performed. The callback will just receive the widget as argument.
|
||
records-displayed: | |||
Records have been displayed. This may be after SqlWidget.reload() or SqlWidget.set_records(). The callback will just receive the widget as argument. See also context changed to see another signal that better tracks any change
|
|||
after-flush: | flush has occurred, normally commit should not add any errors. This is implemented with a SessionExtension: if you used the default session obtained via get_session() you are assured that it will be correct. If you create a session by yourself, be sure to add sqlkit.db.proxy.SKSessionExtension to the session extensions or you won’t have this signal. Read more detailed explanation in hook on_after_flush
|
||
after-commit: | run from within after-commit SessionExtension. Callback signature is identical to after_flush_callback above. |
||
delete-event: | emitted when the sqlwidget is destroyed
|
Beside signals there is another way to add controls: hooks. A hook is a function that will be called in particular moments only if present.
Hooks are the main way to customize the behavior of a sqlwidget. Some of the hooks (on_validation.*) are related to validation other are related to configuration (on init), others (on_activate) may be used to save typing.
Hooks are searched for in the methods of the instance of a class declared in the optional hooks argument or in the global registered hooks (see below).
Hooks can be registered using sqlkit.db.utils.register_hook() (and get_hook from utils module) so that any sqlwidget built on that table will use those hooks (unless the table is part of a join or another selectable!!). The advantage is that browsing data (e.g. using right click on a table row) can lead to opening tables that are not configured: registering hooks is a way to enforce configuration (and possibly constraints) on any widget.
As layout hooks can be registered with a nick (default is default) so that you can register different hooks for different editing flavors. E.g/: you can have a table for people and you can decide to open it with customer or provider layout/hooks just registering both layout and hooks and using argument layout_nick.
Use it with care as it may lead in situation in which not all fields are present (due e.g. to a different layout or different field_list)
The following hooks are defined:
on_change_value__field_name: | |||
---|---|---|---|
called any time the value is set using set_value without initial=True flag. I.e.: should be called any time a value is changed from the original
|
on_completion__field_name: | |||
---|---|---|---|
called when a completion is chosen
|
|||
on_validation: | called when all the values have been collected in the object, before calling validation on all fields and related widgets. That’s a good point to implement any procedure to add automatism’s. Within this hook you can propagate errors to the validation machinery in two of ways:
|
on_field_validation__field_name: | |||
---|---|---|---|
called to validate a single field as for the previous on_validation. This is called from within the field's validation method
|
|||
on_activate__field_name: | |||
when Return is pressed in Mask or Table. Good to complete fields via calculation on other fields (e.g.: total, vat...). The name derive from the GTK name ‘activate’ that is when you press ‘Return’ in an entry, even thought in a Table’s treeview it’s really connected to the cell’s edited signal (limited to Varchar and Numeric columns).
|
|||
on_init: | run as the last command of __init__. It’s main purpose is to allow to configure a widget in a way that will be handed over to a possible SqlMask generated right-clicking from a table row (see RecordInMask).
|
||
on_pre_layout: | run before the layout is setup. It’s main purpose is to allow to add fields vi self.gui_field_mapping (tat is only useful for SqlMask)
|
on_after_flush: | run as the signal with the same name from within after_flush session extension method. This hook is completely similar to after-flush signal, but is meant to be defined in a separate class so that it’s easier to propagate validation hooks to a spawned child (i.e.: RecordInMask, when a mask right clicking on a record, change something and save). You’ll see that on_after_flush is called ether. From the sqlalchemy documentation: “Note that the session’s state is still in pre-flush, i.e. new, dirty, and deleted lists still show pre-flush state as well as the history settings on instance attributes”. This is true for after_commit hook as well, it is not true for after_flush_postexec, that on the other had has already setup relation. I end up in some circumstances to split the callback in two phases: one that detects if an action is needed from within the after_flush/after_commit phase, the second (may be a mail, or any other action) from within the after_flush_postexec, so that I can use the relations. Beware that you may have one call to on_after_flush and more different calls to on_after_commit
|
||
---|---|---|---|
on_after_flush_postexec: | |||
run as the signal with the same name from within after_flush_postexec session extension method As in the precedent hook this is called exactly within the session extension method by the same name. When it’s run the session will have no longer information on session.dirty/session.new/session.delete but will have all relations set-up. The callback has the same signature as for on_after_flush |
|||
on_after_commit: | |||
run after commit after_commit session extension method The callback has the same signature as for on_after_flush |
When hooks are registered their customizations are enforced each time the model for which they’re registered is called. That adds some complications you should be sure to understand if using one of these hooks:
- on_after_flush
- on_after_commit
- on_after_flush_postexec
These hooks are called within a SessionExtension that calls hooks on any sqlwidget that may be using the same session. An m2m, m2o relation table share the same session as the Mask that holds them so that it’s pretty normal to have several different tables within the same session.
From the SessionExtension hooks are searched for in any of these so that you should write the hooks keeping in mind it can be called from another sqlwiget’s commit.
As an example, suppose you have a Mask with the following layout:
first_name
last_name
m2m=genres m2m=actors
suppose movies, genres and actors have registered on_after_commit hooks. They will all be called on any commit. Adding a genre object will trigger on_after_commit on the Actor’s table and vice verse.
Module sqlkit.db.utils provides a simple function that yields all modified attributes of an object, along with their old and new values
Parameters: |
|
---|---|
Return type: | field_name, old_value, new_value. Old value and new value are lists. |
Parameters: |
|
---|---|
Return type: | new_value, unchanged_value, old_value, . Old value and new value are lists. The order is different from get_differences as this is exactly what is returned from the SA function |
Text fields with empty values will be saved as NULL. To change this behavior you need to set blank=True on fields:
t.gui_fields[field_name].blank = True