portal_web.views.forms.datatables package¶
Submodules¶
portal_web.views.forms.datatables.datatable_sequence module¶
- class portal_web.views.forms.datatables.datatable_sequence.DatatableSequence(*args, **kw)[source]¶
Bases:
SequenceSchema
Overview¶
The most distinct UI element of our web framework is the DatatableSequence. On our webpages we needed a solution for 1. records to choose from tables of existing objects (add/remove), 2. create new records, mostly for Many2One object relations (create/edit/
remove)
Technical Basis¶
Chameleon [1]: Template Engine
Deform [2]: Form-Generation-Library
Colander [3]: Backend for validation & De/serialization of data
DataTables: A JavaScript module for interactive html tables
Note: The connections of the first three are shown in detail in the Redmine Wiki [4].
Example
Suppose you have a record ‘person’ with fields ‘id’, ‘name’ and ‘gender’, first you would want to feed the structure to Datatable in you page+ template:
>>> <script> >>> var datatableSequenceSettings = { >>> apiPath: "person", >>> unique: "id", >>> columns: [ >>> { >>> name: "name", >>> title: "${_('Name')}", >>> data: "name", >>> className: "all", >>> orderable: true, >>> searchable: true, >>> render: $.fn.dataTable.render.text(), >>> datatableSequence: { >>> position: "displayed", >>> widgetType: 'TextInputWidget', >>> footerSearch: true, >>> createShow: true, >>> } >>> }, >>> { >>> name: "gender", >>> title: "${_('Gender')}", >>> data: "gender", >>> className: "all", >>> orderable: true, >>> searchable: false, >>> render: $.fn.dataTable.render.text(), >>> datatableSequence: { >>> position: "displayed", >>> widgetType: 'Select2Widget', >>> footerSearch: true, >>> createShow: true, >>> } >>> }, >>> name: "id", >>> title: "${_('ID')}", >>> data: "id", >>> className: "all", >>> orderable: true, >>> searchable: true, >>> render: $.fn.dataTable.render.text(), >>> datatableSequence: { >>> position: "displayed", >>> widgetType: 'TextInputWidget', >>> footerSearch: true, >>> createShow: true, >>> } >>> }, >>> }; >>> </script>
Then a DatatableSequence can be defined:
>>> def prepare_required(value): >>> # oid required for add/edit >>> if value['mode'] != "create" and value['oid'] == "IGNORED": >>> value['oid'] = "" >>> return value >>> >>> @colander.deferred >>> def deferred_location_space_category_widget(node, kw): >>> values = [('m', 'male'), ('f', 'female'), ('o', 'other')] >>> # deferred means that these values don't obey the declarative >>> # context and could be read from a database while the >>> # application is running >>> return deform.widget.Select2Widget(values=values) >>> >>> class NameField(colander.SchemaNode): >>> oid = "name" >>> schema_type = colander.String >>> widget = deform.widget.TextInputWidget() >>> >>> class GenderField(colander.SchemaNode): >>> oid = "gender" >>> schema_type = colander.String >>> widget = deferred_gender_widget >>> >>> class IdField(colander.SchemaNode): >>> oid = "id" >>> schema_type = colander.String >>> >>> class PersonSchema(colander.Schema): >>> name = NameField() >>> gender = GenderField() >>> id = IdField() >>> preparer = [prepare_required] >>> # the preparer sets dummy values in the oid fields in newly >>> # created records >>> >>> class PersonSequence(DatatableSequence): >>> person_sequence = PersonSchema() >>> widget = person_sequence_widget >>> actions = ['create', 'edit']
Finally, the DatatableSequence can be used in your web page:
>>> class CreatePersonSchema(colander.Schema): >>> title = _(u"Create a Person") >>> spaces = LocationSpaceSequence( >>> title=_("Persons"), >>> min_len=1, # at least one person needed >>> language_overrides={ >>> "custom": {"create": _(u"Create It")} >>> }) # allows to inject translations
- Note: When an ‘add’ action is used, you need to implement a special api
for Datatables.
Requirements¶
- Interface for own/foreign objects to the
Display (list)
Create (create)
Edit (edit)
Add (add)
It was crucial that own objects were newly creatable as well as existing foreign objects could be added, preferably in a form. - For ads (list):
client-side sorting
client-side filtering
- For adding (add): Recourse to large data sets with
Server-side paging
Server-side filtering
Since complex objects are created, no reload is desired
and a gradual user guidance becomes necessary - The element is needed several times per object (e.g. release: publisher, labels, tracks, etc.) - Some records require the two-tier embedding of the UI element (e.g. Creation -> Contribution [1st table] -> Artist [2nd table]) - Internationalization - Client side validation if possible
Solution¶
- Methods
- Docking to “Colander/Deform” [5]: Derive objects from Sequence
DatatableSequence(colander.SequenceSchema) [6, 7]
DatatableSequenceWidget(deform.widget.SequenceWidget): [8,9]
- Embedding of “Datatables” [10] for
Tables as GUI-Elements (list) [11] [11
Ajax Handling (add) [12]
Use of “Bootstrap Modals” [13] for step-by-step user guidance
- Benefits of “tpml 14] for client-side templates [15, 16] for
Datatables (e.g. sequence, controls, source/target table, …)
Modals
- Server side
- Colander scheme [6]
Only validation adapted
Deform Widget [7]:
- Sequence template created [17], linked to widget [18]
This is delivered by the JS, which is used to configure everything [19] and is initialized [20]. - Item-Template (Formlar-Template create/edit) is created [21], linked with widget [22]. Basically a normal, recursive Render the form fields. - The function prototype() [23] also generates in a normal sequence [24] the form template, which is sent to the client. must be passed on, because new items client side are added can be added. - De-/Serialization [25, 26] not changed - The function rows() [27, 28] returns the initial record - Internationalization via forwarding the translations as variables to the template engine [29, 30] as JSON, so that the JS-Code that can read and fill the tmpl templates.
- API for Ajax data retrieval via datatables [31]:
Created the schema required by Datatable, which is available for
all Datatable APIs can be used [32, 33].
- On the client side
- Datatables
- Tables: 2 Datatable Tables
- Target: this.target.table (-> list) [11]
The Target Table is the prefilled table
with the current records that are being saved. - It contains the serialized data set that is stored by the Deform widget again and is deserialized. This is contained in an HTML form, the corresponding HTML form elements are created via JS generated and in a separate hidden column is saved.
- Source: this.source.table (-> add) [12].
From the source table are saved when adding a
the data into the Target Table in the existing entry is taken over. - The Source Table contains the entire Ajax Handling in combination with the corresponding server-side API, makes JSON requests to it, takes the JSON records from it and generates the Selection table.
- Columns:
For each individual sequence the desired
columns according to the Datatable Options [34, see “Datatable - Columns”] [e.g. 35], because a automation in most cases does not meet the desired results (“Which columns are currently relevant in the view”?) - Functionally necessary columns are added via JS:
- target [36]:
order: Sorting
more: Show/hide columns (Responsiveness)
controls: Buttons (Edit, Create, Delete)
sequence: Hidden HTML form fields
oid: oid of the sequence
mode: create, edit, add (as a hint for server)
errors: error list
- Source [37]:
more: Show/hide columns (Responsiveness)
controls: Buttons (Edit, Create, Delete)
oid: oid of the sequence
- Modals
There are 3 modals for the actions add, create and edit
While the Target Table is rendered directly on the page
- [38, 39], are
the source table is rendered in add modal [40, 41]
- the form templates each
in create Modal [42, 43], or
rendered in edit Modal [44, 45].
The modals are rendered depending on the action (create, edit, add)
- is called and closed:
create: The form generated by Deform on the server side is
called Template (“Prototype”) is instantiated, with new IDs [46], displayed in the modal and saved in the sequence column. - edit: The either by Deform server generated and filled out, or by create created form is read from the sequence column, displayed in the modal and written back again when saving. - add: The modal with the source table is opened, at Adding an entry will create a new one from Deform on the server generated form template (“Prototype”) is instantiated, with new IDs and with the data of the entry filled out.
- interaction (deform.datatables.widget.js)
Acceptance of the Deform Template Variables [47].
- Initialization [48]:
Generation of the HTML codes from the tmpl templates
Configuration of the columns
Initialization of the tables
Binding of the events
- Mode configuration (“actions=[‘add’, ‘create’, ‘edit’]”)
The controls for the modes available on the client side
add/create/edit’ can be switched on/off [49]. From these The corresponding controls are dependent on switches, Tables and Modals created during initialization or omitted [e.g. 50, 51]. list’ is mandatory, because it without a Target Table no data transfer or GUI would exist.
- Action Functions [52]
This part takes the role of the controller
In the Actions the possible operations on the
- Target Table defined, which the user can trigger:
addRow
removeRow
createRow
editRow
saveRow
This contains the corresponding logic for the
state changes of the GUI element and the User guidance for the modals.
- Events [53]:
Here different functions are defined, which are passed on to
- Datatables Events [54] can be bound, for example
more: Button for hidden columns (Responsive)
search: Initialization of the search at Source Table
redrawAdd: Open Redraw Source Table at Modal
etc.
- Client side validation [55]:
By default, client-side fields are set to required
checked.
- Recursion
For the multi-level GUI, server and
client-side code adapted to support DatatableSequences to be able to nest.
Summary¶
Thus, the standard functionality of Deform/Colander automatically generates a Framework for the delivery of datatables/bootstrap based sequences. DatatableSequences must be defined for each desired object once, then the DatatableSequence can be easily used. This includes:
1. creating the colander scheme [e.g. 56], from which the form template is generated and at least fields for the functional Datatable must contain columns (mode, oid (?))
- Note: The latter could also be defined centrally and used to
as well as the workaround with the prepare Functions [57])
2. create the template [e.g. 58], which contains the JS configuration and must contain at least the following functions:
apiPath: End piece of the path to the API
- Optional:
columns: At least one column for the display is useful
actions: [‘add’, ‘create’, ‘edit’]: Switches for the modes that
should be available on the client side (from the user’s point of view in the sense from ‘add/create/edit entry’) - unique: Column whose content guarantees the uniqueness of an entry identified and, if defined, the multiple addition of prevented. - apiArgs: Communication from JSON Key/Values to the server at Request to the API in add Modal. Can be used for filtering [e.g. 59, 60, 61] or any other purpose of the Client->Server Communication - tpl: For a different tmpl base template prefix. Unlikely that this is needed, but with it other tmpl templates can be used.
3. create the API service [e.g. 62] (if ‘add’ in actions is possible should be).
inclusion of the DatatableSequence [e.g. 63].
Miscellaneous¶
Overwrite the Item-Template, if for each entry
wants to execute certain JS code [e.g. 64]. - Adjusting the displayed text of a column using the render function e.g. 65] (-> Standard Datatables); You can decide about type, which views of the data should be changed [66]. - Client-side debugging/view of globally accessible JS object deform.datatableSequences
References
[1] https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
[2] https://docs.pylonsproject.org/projects/deform/en/latest/
[3] https://docs.pylonsproject.org/projects/colander/en/latest/
[4] https://redmine.c3s.cc/projects/collecting_society/wiki/Pyramid_Concepts#Forms
[5] https://docs.pylonsproject.org/projects/deform/en/latest/widget.html#writing-your-own-widget
[7] https://github.com/Pylons/deform/blob/master/deform/widget.py#L1491
[9] https://github.com/Pylons/colander/blob/master/src/colander/__init__.py#L1095
[11] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L309
[12] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L344
[13] https://getbootstrap.com/docs/3.3/javascript/#modals
[14] https://github.com/blueimp/JavaScript-Templates
[15] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L255
[16] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/backend.pt#L40
[17] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/deform/datatables/sequence.pt
[24]: https://github.com/Pylons/deform/blob/master/deform/widget.py#L1559
[32] https://datatables.net/manual/server-side
[34] https://datatables.net/reference/option/
[36] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L413
[37] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L536
[38] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L257
[39] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/backend.pt#L58
[40] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L270
[41] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/backend.pt#L100
[42] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L280
[43] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/backend.pt#L165
[44] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L289
[45] https://github.com/C3S/portal_web/blob/develop/portal_web/templates/backend.pt#L180
[47] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L99
[48] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L227
[49] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L159
[50] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L265
[51] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L345
[52] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L593
[53] https://github.com/C3S/portal_web/blob/develop/portal_web/static/js/deform.datatables.widget.js#L819
[54] https://datatables.net/reference/event/
[66] https://datatables.net/manual/data/orthogonal-data
- validator = <colander.deferred object>¶