- A. Introduction - some concepts
- B. Creating a new plugin
- C. Plugin file structure
- D. Configuring a plugin
- E. Programming the client side Event Management pages
- F. Programming the server side
- G. Other programming
- H. Utility functions available
- I. Other tasks performed by the core
(Note: this page is currently under construction)
A. Introduction - some concepts
A.1. What is a plugin?
Shortly put, a plugin is a part of Indico that is optional. They usually deal with external systems that the organization using Indico may or may not have. Therefore, organizations that use Indico can easily decide to use or not use them, or even remove their code from Indico.
Plugins in Indico are organized into Plugin Systems. Collaboration is one of those systems, and deals with videoconference systems (where you can book videoconferencing resources) and Service Requests such as a request to record an event. The first kind of plugins deal with external systems and use these system's APIs. The second kind of plugins usually help communicate (for example by email) a service responsible and an event manager. Other Plugin Systems in Indico include Epayment and Room Booking.
A.2. The Collaboration plugins core
The Collaboration plugins core (or core for short in this guide) is the part of Indico that interacts with the plugins:
- It defines the interface between Indico and the plugins.
- It calls the plugins' functions when it is necessary.
- It takes care of many common tasks so that the plugins do not have to implement them.
- It provides helper / utility functions to the plugins.
The core is present both in the server side (Python code) and the client side (HTML, CSS and Javascript code).
A.3. Bookings and requests
Among the Collaboration plugins, there are two sub-types of plugins so far:
- Booking plugins: they provide the user with a form where they can book resources in an external system. In most cases, there can be multiple bookings of the same type for the same Indico event. This means that in the database, there will be multiple "Booking" objects of the same type associated to an event.
- Request plugins: they provide the user with a form where they can request a service. For each Indico event, there can be only one Request of a given type.
Examples of Booking plugins are: EVO, CERNMCU, Vidyo. Examples of Request plugins are Recording Request and Webcast Request.
A.4. Pages generated by Collaboration plugins
The Collaboration plugins will generate pages / information that will appear in several places in Indico:
- In the management interface of an Indico Event, in the Video Services section (left menu). Each plugin will appear in a given tab. Several plugins can share a tab (such as EVO, CERNMCU and Vidyo) or be alone in their own tab (like Recording Request).
- In the display page of events, for the Booking plugins. When a booking has been done in an external system, the Event manager can decide to show information about it on the event's display page to allow normal users to join the Videoconference.
- In the Server Admin interface, in the Plugins section, under the Collaboration tab. Each plugin will be represented in a sub-tab under which the plugin's options and actions will appear.
- In the Video Services Overview page, where Bookings and Requests are indexed (if the respective plugin wishes to) and queries can be made to have a global view.
B. Creating a new plugin
To create a new plugin, the best way to start is to copy one of the existing plugin's folder.
For example, if developing a new Videoconference booking plugin, one may make a copy of the EVO folder in MaKaC/plugins. If developing a new request plugin, one may copy the RecordingRequest folder.
Choose a name for your plugin, and rename the folder to this name.
Then head to the __init__.py file inside your plugin folder. Change the pluginName variable to the name of your plugin, and the pluginDescription variable to some description of your plugin that will be visible in the Server Admin > Plugins web interface.
It is strongly advised to use the same name for the plugin folder and the pluginName variable.
Voilà, you have a new plugin! However if you started creating a folder from scratch you should read the next section which explains the different files inside the folder, many of them are compulsory.
C. Plugin file structure
This section explains the different files of a plugin, giving a short summary for each and specifying if a given file is optional or compulsory. Please note that more files can be added at the programmer's discretion; all the files inside the plugin folder will be imported by the plugin system.
C.1. Compulsory files
- __init__.py: your plugin folder is a Python package, and as any Python package it must have a __init__.py file. Here you also have to define some important attributes of the plugin such as its title, etc. as described in the General Plugin system guide.
- collaboration.py: this is the most important file of your plugin, and the core of the server-side operations. Here you will have to implement your CSBooking class (CSBooking = Collaboration Systems booking), which has to inherit from CSBookingBase. This class serves 3 main purposes:
- Define what information is stored in the DB for a booking / request that is attached to an Indico event. The information that comes from a booking creation form / request sending form will be stored into its self._bookingParams dictionary. You can of course also store other information as attributes of your CSBooking object. More information in the Programming the Server Side section.
- It is the interface between the Indico core and the external system. When a booking is created, modified, deleted, etc. the core will call methods implemented in this class, such as self._create(), self._modify(), self._delete(), etc. More information in the Programming the Server Side section.
- Finally, this class will have class attributes that tell the Collaboration core how to handle bookings of this class, as explained in the Plugin characteristics section.
- options.py: in this file you define your global configuration options, which are values stored in the DB common to all of your plugin operations, no matter what the Indico event is. Examples of such values would be: the URL of the remote system your plugin deals with, the login and password that you must use to access that system, etc. The syntax to declare the options is described in the General Plugin system guide. All Collaboration plugins must always have 3 options declared: tab, allowedOn, and admins, as explained in the Plugin options section.
- pages.py: this is a Python file where you have to implement a set of "W" classes. Each of which will correspond to a .tpl file, following the same pattern as normal Indico pages. For example, you will have to implement a WNewBookingForm class that corresponds to the NewBookingForm.tpl file. Contrary to the rest of Indico, in the Collaboration plugins it is also possible to define "W" classes for your .js (Javascript) files. This file also hosts the XMLGenerator class, which is charged with rendering XML with a booking's information that will be displayed in the display page of meetings and lectures. More information in the Programming the client side section and the Display pages section.
- tpls/NewBookingForm.tpl: this is a Python / HTML template file where you design how your booking creation / modification form, or your request form will look like. You do not need to put <form> tags around your <input> elements; you just need to give the <input> elements a name attribute and the core will automatically store those values in the self._bookingParams attribute of the CSBooking class, using the name attribute as key of the dictionary. More information in the Designing your dialog / form section.
- tpls/Main.js: this file contains a dictionary of functions that will be called by the core on the client side when certain events happen, such as: when the dialog / form is constructed, when the user presses "Save" / "Send", etc. This file corresponds to the WMain class in pages.py. More information in the Javascript interface between core and plugins section.
C.2. Optional files
- actions.py: in this file you define your global actions, pieces of code that can be executed by server admins. The syntax to declare an action, implement its code and when it is executed are all explained in the General Plugin system guide.
- common.py: in this file you should write functions that will be used in several Python files of your plugin, for example functions that will be used both in collaboration.py and pages.py. In that regard, it does not have to be called common.py, with one exception: if you decide to implement a GlobalData class, the file does have to be called common.py. More information in the Global Data section of the General Plugin system guide.
- fossils.py: in this file you should include the fossils that will be used to turn your Python objects into Javascript objects. The file does not necessarily have to be called fossils.py, with one exception, which is if your plugin allows the core to index its bookings and you need to overload the ICSBookingConfModifFossil interface that is used to fossilize the bookings displayed in the Video Services Overview page, for example to hide the password information. More information in the Indexing section.
- services.py: in this file you can write your Service classes. These are server-side classes that answer AJAX requests done by your plugin. More information in the sections: Calling Services and Custom services in the server.
- tpls/Extra.js: this file is the place to put any Javascript code that does not fit well in Main.js, or needed to further customize your booking dialog / request form. It corresponds to the WExtra class in pages.py. The Javascript code written here will be automatically inserted in the <head> of the page. More information in the Custom Javascript section.
- tpls/Indexing.js: if your plugin allows indexing, the core will look at functions declared in this file when printing the bookings in the Video Services Overview page. Its structure is similar to the Main.js file and it corresponds to the WIndexing class in pages.py.
- tpls/InformationDisplay.tpl: if you want to display information about your bookings in the event pages, you will have to write this template file, which will be used to display information about bookings in conference events. For meetings and lectures, this has to be done through the XMLGenerator class. More information in the Display pages section.
- Style.css: in this file you can write custom CSS rules for your dialog / form. It will automatically be inserted in the page's <head>. It corresponds to the WStyle class in the pages.py file.
D. Configuring a plugin
D.1. Plugin characteristics
Different plugins cover different concepts or have different needs. In the collaboration.py file of each plugin, inside the CSBooking class, several class attributes can be defined that will help the Collaboration core know what to expect when dealing with the plugin.
All these class attributes are defined with their default values in the CSBookingBase class inside MaKaC/plugins/Collaboration/base.py. If you like the default value of a class attribute, you do not need to write it again in your CSBooking class, although sometimes it is not bad to do it anyway, for clarity.
List of class attributes and their functions:
- _allowMultiple: True by default. Should be False if the plugin does not wish to allow that more than one CSBooking object of its type should be created for each Indico Event. This is what distinguishes Booking-type plugins from Request-type plugins.
- _hasStart: False by default. Should be True if the plugin has a "start" concept, which means a "Start" button will appear next to a created Booking, allowing the Event Manager to start the Videoconference.
- _hasStop: False by default. Should be True if the plugin has a "stop" concept, which means a "Stop" button will appear next to a created Booking, allowing the Event Manager to stop the Videoconference.
- _hasStartStopAll: False by default. Should be True if the plugin has _hasStart == True and/or _hasStop == True, and they want "Start All" / "Stop All" buttons to appear for their bookings. These buttons will allow to start / stop all of the bookings at once (of all the plugins in the same tab who have _hasStartStopAll == True).
- _requiresServerCallForStart : False by default. Should be True if the plugin has _hasStart == True and wishes that the CSBooking object on the server side should be notified when the Event Manager clicks on the "Start" button, by calling CSBooking 's _start() method.
- _requiresServerCallForStop : False by default. Analogous to _requiresServerCallForStart, but for the "Stop" action.
- _requiresClientCallForStart : False by default. Should be True if the the plugin wishes a Javascript function to be called when the Event Manager presses the "Start" button. The function called will be the start() function in Main.js.
- _requiresClientCallForStop : False by default. Should be True if the the plugin wishes a Javascript function to be called when the Event Manager presses the "Stop" button. The function called will be the stop() function in Main.js.
- _hasCheckStatus: False by default. Should be True if the plugin has a "check status" concept, which means a "Check status" button will appear next to the Booking / Request status, allowing the Event Manager to reload all the information of the Booking / Request from the Indico Server into his browser.
- _hasAcceptReject: False by default. Should be True if the plugin has a "check status" concept. In that case, "Accept" and "Reject" buttons will appear next to the Booking / Request status, only visible to plugin admins or server admins, who can use them to change the state of the Booking / Request object to "Accepted" or "Rejected".
- _needsBookingParamsCheck : False by default. Should be True if the the plugin wishes to check the Booking / Request parameters in the server after the booking is created / edited. In that case, the _checkBookingParams() method of the plugin's CSBooking class will be called to verify the parameters.
- _needsToBeNotifiedOnView: False by default. Should be True if the plugin wishes its CSBooking objects to be notifies when an user "sees" the booking, for example when returning the list of booking. The _notifyOnView() method of the plugin's CSBooking class will be called.
- _shouldBeIndexed: True by default. Should be False if the plugin does not wish to allow its CSBooking objects to be indexed, i.e. to appear in the Video Services Overview page.
- _commonIndexes: [] by default. A list of strings. Useful when _shouldBeIndexed == True. If left empty, the plugin's CSBooking object will appear in the "All" index and in the plugin's own index, but not in any shared index. If set to ["Videoconference"], for example, they will also appear in the common "Videoconference" index. For example, currently EVO, CERNMCU and Vidyo bookings appear in this index.
- _hasStartDate: True by default. Should be True if the plugin's CSBooking objects have a start date attribute. This will allow the Collaboration Core to index these objects by start date.
- _hasEventDisplay : False by default. Should be True if the plugin's CSBooking objects wish to be displayed in the Event's display page.
- _hasTitle: False by default. Useful if _hasEventDisplay == True. Should be True if the plugin's CSBooking objects have a Title. This will affect how the objects appear in the Event display page.
- _adminOnly: False by default. If set to True, only Server Admins, Video Services Admins, and Plugin Admins (for the corresponding Plugin) will be able to interact with the plugin / see the plugin's tab in the Video Services section of the events. It will not stop normal users from seeing information about bookins in Events Display pages (if any, and if the booking is not Hidden).
It is recommended to see the values of these class attributes defined by each existing plugin in their collaboration.py file.
D.2. Plugin options
As other types of plugins, Collaboration plugins can define Options with different attributes. The options' values will be stored in the database. Options are useful to store things such as the remote system URL, the plugin list of admins, etc. In other words, things that should be in the database not attached to any Event in particular, but of global scope in Indico.
Options are defined in the options.py file. More information on the declaration syntax, and how to read their values in your plugin's code, can be found in the Plugin System page, in the corresponding section.
It's compulsory for Collaboration Plugins to declare the following options:
- tab: the name of the tab, in the Video Services section of an Indico Event's management interface, that this plugin will appear in.
- allowedOn: a list of one to three strings. The strings inside the list can be: "conference", "meeting" or "simple_event" (this last one representing lecture events). The Collaboration Core will allow the plugin to appear only for these kind of events.
- admins: put an empty list as defaultValue. Here the plugin admins (Avatar objects) will be stored.
E. Programming the client side Event Management pages
This section will describe how to design and implement the pages and dialogs that your plugin will show in the Video Services section of an event's management interface.
We will cover both how to render the initial dialog / page in the browser, the Javascript that will be called on different occasions, and how to call services (AJAX requests) on the server.
E.1. Dialog vs page form
Depending on the the value you chose for CSBooking._allowMultiple (see D.1.), your plugin will be shown differently in the Video Services section.
If CSBooking._allowMultiple == True, you are allowing several CSBooking objects for the event. Most Booking plugins will fall into this category. Therefore, it makes sense that these bookings will be shown in a list. The list will look like this:
Pressing the "create" button will open a creation dialog, with a form to fill in the booking details. This dialog will also be used when editing an existing booking.
However, if CSBooking._allowMultiple == False, you are only allowing one CSBooking object of that type for the event. Most Request plugins will fall into this category. In this case, the form to fill in the details about the booking / request will appear directly in the webpage, without a button to open a dialog.
The way you write your code if greatly affected by your choice in this regard.
E.2. Designing your dialog / form
E.2.1. The NewBookingForm.tpl file
The HTML code that will appear in your dialog / form is in the NewBookingForm.tpl file.
In this file, you can write mixed HTML and Python code, like any other .tpl file in Indico. You can do conditions and loops in Python in order to generate your HTML.
If you use any Python variables in this file, they have to be declared in the WNewBookingForm class in the pages.py file. It is useful to use Python variables in order to have default initial values in the form fields, for example.
If you include <input> elements in your form, please note that the name attribute that you choose for them is important: it has to correspond with the booking parameter name that you will declare in your CSBooking Python class. See the Booking Parameters section for more details.
When the user presses the "Save" button (for Booking Plugins) or the "Send Request" or "Modify Request" buttons (for Request Plugins), an AJAX request is sent by the core to the server. The values inside the fields with a "name" attribute is automatically read and sent to the server.
In a similar way, when an "edit booking" dialog is openened, or we visit a request form from a Request Plugin after the request has been sent, the core will automatically fill the fields with the values already stored in the CSBooking object, without the plugin having to implement anything for this to happen.
An important note: because of the way the HTML code has to be injected into the dialogs, any <script type="text/javascript"> blocks will NOT work in NewBookingForm.tpl. They will however work if your plugin's form is not a dialog but a page. You can however add Javascript inside things such as onclick attributes and so on. The rest of the Javascript, you will have to write in the Main.js and Extra.js files.
E.2.2. The WNewBookingForm class
Inside the pages.py file, even if you do not need any Python variables inside NewBookingForm.tpl, you must declare a WNewBookingForm class that inherits from WCSPageTemplateBase:
from MaKaC.plugins.Collaboration.base import WCSPageTemplateBase class WNewBookingForm(WCSPageTemplateBase): def getVars(self): vars = WCSPageTemplateBase.getVars(self) ## Assign your variables into the vars dictionary here, example: vars["Title"] = self._conf.getTitle() return vars
The getVars() method is only necessary if you need to have Python variables in NewBookingForm.tpl (which is most often the case anyway).
Because you are inheriting from WCSPageTemplateBase, the following attributes are available for you in self:
- self._conf: a Conference object. The event you are working with.
- self._user: an Avatar object. The currently logged in user (there's always one since we are in the Management interface).
- self._pluginName: a string with the name of your own plugin.
- self._ph: the PluginsHolder object, that you can use to access information about all the plugins (see the Plugin System guide)
- self._plugin: a Plugin object. Your own Plugin object, actually. See the Plugin System guide again.
- self._XXXOptions: a dictionary whose values are the plugin's PluginOption objects, which you could access through self._plugin but the WCSPageTemplateBase class build for you for convenience. Replace XXX by your plugin's name.
E.2.3. The AdvancedTab.tpl file and the WAdvancedTab class
For Booking plugins, it is possible to also include plugin-defined content in the "Advanced" tab of the booking creation / edition dialog.
For this, create a file called AdvancedTab.tpl in your tpls folder and a WAdvancedTab class in pages.py that inherits from WCSPageTemplateBase. This file & class combination works the same way as the NewBookingForm.tpl file and the WNewBookingForm class. Fields / widgets in the "Advanced" tab will be detected by the Collaboration core in the same way as those of the "Basic" tab (which are those included in NewBookingForm.tpl).
If you do not include an AdvancedTab.tpl file in your plugin, the ConfModifCollaborationDefaultAdvancedTab.tpl file will be used as default. This template includes the checkboxes for Synchronize booking to event and Hide booking that were the only ones present in previous versions of the Collaboration core. Moreover, you can include this default template in your own one, in order to have these checkboxes available in your plugin, like this:
<!-- Contents of AdvancedTab.tpl --> ... <% includeTpl('ConfModifCollaborationDefaultAdvancedTab') %> ...
E.3. Javascript interface between core and plugins
On certain events, such as when the user clicks on the Create button to create a booking, or when the Send Request button is pressed to send a request, certain Javascript functions from your plugins will be called by the core.
These functions are placed in the Main.js file. So that the core can call them, they have to have particular names. The functions must be organized into an object / dictionary, for example:
//Contents of the Main.js file: { checkParams: function() { // function code here }, errorHandler: function(event, error) { // function code here } }
Important note: never put a comma after the "}" of the last function! Firefox will not catch error but IE will and it will not be easy to debug without appropriate tools. Same goes for any list or object declaration: never put a comma after the last element. This happens often when you copy/paste without paying attention.
You will need to create a WMain class in your plugin's pages.py file that inherits from the base class WJSBase. The WJSBase constructor will set for you the attributes self._conf with the Conference object and self._user with the currently logged in user. In your WMain class, you can have a getVars method where you return a dictionary with the Python variables that you can use in the Main.js file (see the section about the WNewBookingForm class for more details). Therefore, Main.js is both a Javascript file and a template file.
The Javascript functions of the Main.js file are the following. All of them, except errorHandler, can be declared or not optionally. The core will check if they exist or not. Many of the functions are passed a booking object, which is a fossilized CSBooking instance.
- checkParams(): this function will be called by the core to perform checks on the parameters input by the user in the booking creation / edition dialog or in the request form, before an AJAX request is sent to the server. More details in the Parameter checking in the client section.
- errorHandler(event, error, booking): this function is called when the CSBooking class's methods: _create, _modify, _checkStatus, and _delete return an instance of the CSErrorBase class. The event argument will have one of the following values: "create", "edit", "checkStatus" and "remove", depending on the action that triggered the error. The error argument will have the fossilized CSErrorBase object, that we can use to obtain aditional details about the nature of the error. Finally, in the latest versions of the core, a booking parameter is also passed, containing the fossilized CSBooking instance of the booking / request whose operation triggered an error. More details in the Controlled errors section.
- customText(booking): this function should return the text that will appear either in the Info column for Booking plugins, or under the request status for Request plugins.
- showInfo(booking): if this function is present, an "expand info" button [ + / - ] will appear for bookings of Booking plugins. When this button is pressed, the showInfo function will be called with the corresponding booking as argument and is expected to return whatever HTML should be displayed. The returned HTML can be in two formats: either a plain string that can contain HTML tags, or a Presentation's XElement, for example an Html.table() XElement. The latest option allows for a cleaner way of building the displayed information.
- getDateFields(): if this function is present, it should return a list of names of fields for which the core will apply a "Calendar" widget that will help the user choose a date. For example, the EVO plugin has the following two fields:
<input type="text" size="16" name="startDate" /> <input type="text" size="16" name="endDate" />
and its getDateFields function returns the array ["startDate", "endDate"].
- onCreate(bookingPopup): if present, this function will be called by the core after the booking creation / edition dialog has been drawn. It has no effect for Request Plugins. This function is useful for:
- drawing complex fields implemented in Javascript, such as a ShowablePasswordField (see EVO and CERNMCU plugins for examples) or a participant list. The function is passed a bookingPopup object so that the plugin can call its addComponent(component) function, in order to add a Javascript field to the list of fields for which the values will be verified by the checkParams() function and whose values will be sent to the server when the "Save" button is closed, along with the values from normal <input> elements. If you implement your own Javascript Field type, be aware of the following conditions:
- It has to inherit from the ErrorAware mixin class,
- It has to have a getName() method (so that the core can treat is as an <input> with a "name" attribute),
- It has to have a get() method (so that the core can read its value).
- It has to have a set() method (so that the core can set its value when editing a booking / modifying a request).
- rendering the "?" icons for Context help.
- drawing complex fields implemented in Javascript, such as a ShowablePasswordField (see EVO and CERNMCU plugins for examples) or a participant list. The function is passed a bookingPopup object so that the plugin can call its addComponent(component) function, in order to add a Javascript field to the list of fields for which the values will be verified by the checkParams() function and whose values will be sent to the server when the "Save" button is closed, along with the values from normal <input> elements. If you implement your own Javascript Field type, be aware of the following conditions:
Let's see an example with the contents of the EVO plugin's onCreate function:
onCreate: function(bookingPopup) { var EVOPasswordField = new ShowablePasswordField('accessPassword', '', false); //utility class in htdocs/js/indico/Core/Widgets/Inline.js. Will be treated as an <input> field with name "accessPassword". $E('passwordField').set(EVOPasswordField.draw()); //we change the contents of a div in NewBookingForm.tpl with id="passwordField" bookingPopup.addComponent(EVOPasswordField); EVODrawContextHelpIcons(); //declared in Extra.js }
- onEdit(booking, bookingPopup): if present, this function will be called by the core after the booking creation / edition dialog has been rendered, but before the core will set the values of the fields from the existing booking object. Its purpose is similar to onCreate: render complex Javascript fields, tell the BookingPopup? object to be aware of them via the addComponent method, and draw the "?" icons. Additionally, it receives the 'booking' argument with the fossilized booking that is being edited.
- onSave(values): if present, this function will be called for Booking plugins when the user has pressed the "Save" button, after the checkParams function has been called and gave no errors. The dictionary that the core has built with the values recovered from the fields is passed as a values argument. The function can add extra key:value pairs to values. This is useful to add information that will be sent from the server from fields that could not be added to the BookingPopup? via the addComponent function. This function should return true by default; if false is returned, the AJAX call to the server will not be performed, because the core will assume that onSave did some kind of parameter check that failed.
- onLoad(): if present, this function will be called for Request plugins after the form request page has been loaded.
- clearForm(): if present, this function will be called for Request plugins after the a request has been withdrawn via the "Withdraw Request" button. If may be interesting to call this function from the onLoad function when the request does not exist yet, as browsers usually cache the state of input fields.
- postCreate(booking): if present, this function will be called after the AJAX request to create a booking / send a request was performed successfully, without exceptions or controlled errors. The resulting fossilized CSBooking instance will be passed to the function. This may be interesting to show a warning to the user by consulting the warning attribute of the booking. See Warnings for more details.
- postEdit(booking): similar to postCreate, will be called after a successful AJAX request to modify a booking or request.
- postDelete(booking): similar to postCreate and postEdit, will be called after a successful AJAX request to delete a booking or withdraw a request.
- postCheckStatus(booking): similar to postCreate,postEdit and postDelete, will be called after a successful AJAX request to query the status of a booking or a request.
- start(booking, iframeElement): will be called by the core for Booking Plugins that have a "Start" button, only if the _requiresClientCallForStart class attribute of your CSBooking class is true, and the booking's _permissionToStart instance attribute is True. We pass the fossilized booking and an iframeElement where we can launch a download (such as EVO's Koala client).
- checkStart(booking): if present, will be called by the core before calling start. This function has to return true to allow the start or false otherwise.
- stop(booking, iframeElement): analog to the start function.
E.4. Custom Javascript
You will probably need to write extra Javascript code outside the standard Main.js functions: for example, you may have code that is used by several other functions, or you may want to define a class using Presentation's type keyword (see the Presentation quick guide).
In these cases you have 2 options:
- Create new functions in the function dictionary of Main.js and call them via the callFunction(pluginName, functionName, arguments) helper function.
- Create an Extra.js file in your plugin's tpls folder. Any Javascript code written there will be included in the <head> of the management pages. For this, you will also need to create a WExtra class in your plugin's pages.py file that inherits from the base class WJSBase. The WJSBase constructor will set for you the attributes self._conf with the Conference object and self._user with the currently logged in user. In your WExtra class, you can have a getVars method where you return a dictionary with the Python variables that you can use in Extra.js (see the section about the WNewBookingForm class for more details). Please note that self._conf may be None when the Extra.js Javascript code is included in the Video Services overview page for Collaboration admins. Therefore, Extra.js is both a Javascript file and a template file.
E.5. Calling services
In many cases, you do not need to program service calls (AJAX requests) in your plugin, since the Collaboration core will take care of the most common AJAX calls (creating / editing / reading / deleting a booking, etc.) or you can use some of the already developed services (like getting the title of an event). However, if you do need to develop a service, it is also possible.
In this section we will discuss how to call a service in Javascript. The implementation of the service in the server is discussed in section F.6. Custom services in the server.
In Indico, a service call is done through the indicoRequest call:
var killProgress = IndicoUI.Dialogs.Util.progress($T("Please wait a moment...")); indicoRequest(serviceName, arguments, function(result, error){ // hook function if (!error) { // do stuff with the result killProgress(); } else { // possibly do stuff about the error, or: killProgress(); IndicoUtil.errorReport(error); } } );
Plugin service calls are no different except for the following constraints:
- serviceName has to be always "collaboration.pluginService". "collaboration.pluginService" is a service from the Collaboration core that will dispatch request to the custom services implemented by the plugins.
- inside the argument object, there has to be always at least these 3 items: plugin, service, and conference. Example:
{ plugin: 'RecordingRequest', // the name of your plugin service: 'RRTalks', // the name of your service conference: '<%= ConferenceId %>' // the conference Id of the Event you are working this. You can generate this with Python code in your .tpl, Main.js or Extra.js files if you want. }
The rest is the same as normal Indico service calls. When the server returns the result (or an error), the function(result,error) hook function will be called.
E.6. Context help
We call "context help" the little "?" icons that are often put in web applications next to form fields / elements that the user may not understand at first sight. In Indico, these icons show a help tooltip when the mouse is hovered above them.
The Collaboration core does not offer a mechanism to quickly say where these icons should be and what text they should have, because it is actually a not very complex process:
- Include the "?" image in your NewBookingForm.tpl or AdvancedTab.tpl files, giving it an id:
<img id="startDateHelpImg" src="<%= systemIcon('help')%>" style="margin-left:5px; vertical-align:middle;" />
- Create a function in your Extra.js file that adds the tooltips to the images, and for each image create a function that builds the tooltip, such as this:
/** * Mouseover help popup for the 'Start date' field */ var EVOStartDateHelpPopup = function(event) { IndicoUI.Widgets.Generic.tooltip(this, event, '<div style="padding:3px; width: 300px;"">' + $T('Please create your booking between <strong><%= MinStartDate %></strong> and <strong><%= MaxEndDate %></strong> ' + "(Allowed dates \/ times based on your event's start date and end date). " + 'Also, please remember the start date cannot be more than <%= AllowedStartMinutes %> minutes in the past.') + '<\/div>'); }; ... /** * Draws the context help icons and assigns the appropiate popups to each one. */ var EVODrawContextHelpIcons = function() { $E('startDateHelpImg').dom.onmouseover = EVOStartDateHelpPopup; ... }
- Call that function from the functions onCreate, onEdit (for Booking plugins) or onLoad (for Request plugins) of your Main.js file (see an example in the onCreate function explanation
F. Programming the server side
F.1. Booking parameters. Information stored in the DB
The CSBooking class that every plugin has to implement represents the bookings or requests inside an Indico event. The instances of this class store information of two types:
- Booking parameters: they are the values filled in by the user in the booking creation / edition dialog or the request form. The core will automatically read them from the dialog / form and store them in your CSBooking class.
- Other information: you may want to store other information as attributes of your CSBooking instances, for example: information derived from operating the booking parameters, or information retrieved from a remote system.
The following two sub-sections will explain how to declare in your CSBooking class which are the booking parameters of your plugin.
F.1.1. Simple parameters
Simple booking parameters are those who do not need any special conversion between the value written by the user in the dialog or form and the value stored in the CSBooking instance.
The Collaboration core will parse the values input by the user and will store them in the self._bookingParams attribute of the CSBooking class. self._bookingParams will be a dictionary where the key is the name attribute of the corresponding HTML input element in NewBookingForm.tpl or AdvancedTab.tpl.
So, for example, if in NewBookingForm.tpl we have the following text input field:
<input type="text" name="bookingTitle" />
we will be able to access in the methods of our CSBooking class like this:
title = self._bookingParams["bookingTitle"]
Please note, however, that the recommended way to recover a parameter is to use the getBookingParamByName function:
title = self.getBookingParamByName("bookingTitle")
which will return a default value if this CSBooking object does not have a parameter of that name (which can happen if for some reason that parameter did not arrive from the client).
Simple booking parameters need to be declared as a CSBooking class attribute, so that the core needs if a parameter arriving from the client is legal or not, which type it has, and what is the default value. The syntax is:
_simpleParameters = { "paramName1": (paramType1, defaultValue1), "paramName2": (paramType2, defaultValue2), ...
where the "paramType" should be a Python primitive type (str, int, bool, list). This type will be used to verify that the parameter arriving from the client is compatible with the given type, and to convert this value to the given Python primitive type.
The bool type is special because the core assumes that a bool parameter will be used for a single checkbox in either NewBookingForm.tpl or AdvancedTab?.tpl. When getting the booking parameters, a value of True will be converted to yes? so as to properly mark the checkbox as checked.
The default value will be used when a parameter is not present in an already existing CSBooking instance. For example, between different versions of your plugin you may have added new parameters but you already had existing CSBooking instances in the database.
The default value will also be used if a booking parameter is not present in the dictionary coming from the client. For example, if a checkbox in your dialog / form is not checked, no value will arrive from the client for that paramater name and the core will use the default value.
Example for the CERNMCU plugin:
_simpleParameters = { "name": (str, ''), "description": (str, ''), "id": (str, ''), "displayPin": (bool, False)}
Example for the RecordingRequest plugin:
_simpleParameters = { "talks" : (str, ''), "talkSelectionComments": (str, ''), "talkSelection": (list, []), "permission": (str, ''), "lectureOptions": (str, ''), "lectureStyle": (str, ''), "postingUrgency": (str, ''), "numRemoteViewers": (str, ''), "numAttendees": (str, ''), "recordingPurpose": (list, []), "intendedAudience" : (list, []), "subjectMatter": (list, []), "otherComments": (str, '')}
F.1.2. Complex parameters
Sometimes a parameter has a complex structure, or setting / reading its value should have side effects. In short, the plugin needs to handle itself how to set / get the parameter value with its own getter and setter method. In this case we speak about a complex parameter.
A complex parameter also needs to be declared in the CSBooking class, via the _complexParameters class attribute. The syntax is:
_complexParameters = ["paramName1", "paramName2", ...]
When you declare a parameter name as a complex parameter, you need to implement the corresponding "getter" and "setter" methods. So for example, if a complex parameter is called "description", we would need to implement both a method called getDescription and a method called setDescription. The method setDescription will be called when the core is reading the data coming from the client in a booking creation / booking modification / request sending / request modification request, and will be passed a single value which is the new parameter value. The method getDescription will be called when the core needs to render an existing booking / request.
F.1.3. Special parameter names
- startDate and endDate: if inside NewBookingForm.tpl or AdvancedTab.tpl you have <input> fields whose names are "startDate" and / or "endDate", they are treated specially by the core. This is in order to help the plugin with the potentially complex task of transforming a text string to a Python timezone-aware datetime object and viceversa. Therefore, you do not need to declare them in _simpleParameters or _complexParameters; they will be automatically stored in your CSBooking instance and available via the CSBookingBase methods getStartDate and getEndDate.
- hidden: the parameter name "hidden" is used by the core for Booking Plugins for a checkbox that appears in the "Advanced" tab of the booking creation / edition dialog. Therefore, you should not have a plugin-defined parameter with the name "hidden".
- notifyOnDateChanges: the parameter name "notifyOnDateChanges" is reserved in the same way as "hidden".
F.1.4. Notes about booking parameters handling
The functions of the core that handle setting and getting the booking parameters are inside the CSBookingBase class. They are mainly the methods: setBookingParams, getBookingParams.
The method getBookingParamByName of CSBookingBase should be use when safely retrieving a booking parameter. If the given booking parameter name is not a key of the self._bookingParams dictionary, the default value will be returned instead.
F.2. Implementing methods
Besides booking / request parameter handling, the other main purpose of the CSBooking class is to implement methods that will be called by the core on certain events, such as when the booking / request is created, updated, queried, deleted, etc. For example, if your plugin deals with a remote system, you probably want to call an operation of that system's API when a user creates a booking in Indico.
This is the list of such methods:
F.2.1. _checkBookingParams(self)
The _checkBookingParams(self) method this will be called by the core when the user is creating or modifying a booking or sending a request, only if the CSBooking class attribute _needsBookingParamsCheck is set to True (see Plugin characteristics). When this method is called, the following has already happened:
- An instance of your CSBooking class has been constructed via its __init__ method, and it has an internal per-Indico-event id that you can recover with the self.getId() method.
- The sanitizeParams method of the CSBookingBase class has been called and the sanitization check was successful (see /wiki/Dev/Technical/CollaborationPluginsDevelopment#I.4 Parameter sanitization].
- All the booking parameters values (simple and complex) have been set.
These operations are performed by the createBooking or changeBooking methods of the core class CSBookingManager.
In the _checkBookingParams method you should implement whatever checks need to be done when a booking / request is created or modified in Indico. You should assume that the Javascript verifications done in the client side of your plugin did not work and check everything again in the server. You may also want to implement extra checks that for some reason can only be performed in the server side.
The _checkBookingParams method should return:
- False (not None!) if there have been no problems,
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. There will be no database commit.
- (only in the most recent versions of the core) an instance of CSErrorBase if there was a problem and we want to convey information to the client of the plugin. The errorHandler function in Main.js will be called. (in older versions, this is equivalent to raising an exception).
More information in the Error Handling section.
F.2.2. _create(self)
The _create(self) method this will be called by the core when the user is creating a booking or sending a request. When this method is called, the following has already happened:
- An instance of your CSBooking class has been constructed via its __init__ method, and it has an internal per-Indico-event id that you can recover with the self.getId() method.
- The sanitizeParams method of the CSBookingBase class has been called and the sanitization check was successful (see /wiki/Dev/Technical/CollaborationPluginsDevelopment#I.4 Parameter sanitization].
- All the booking parameters values (simple and complex) have been set.
- The self._checkBookingParams method has been called and the verification was successful.
These operations are performed by the createBooking method of the core class CSBookingManager.
In the _create method you should implement whatever needs to be done when a booking / request is created in Indico, such as:
- Calling an operation of the remote system API,
- Setting attribute values, such as information returned by the remote operation, or the self._statusMessage and self._statusClass attributes (see Special CSBooking instance attributes.
- Sending notification emails (to the event creator, the plugin admin...).
The _create method should return:
- None if there have been no problems. In this case, the core will fossilize the modified booking / request and return it to the client.
- an instance of CSErrorBase if there was a problem and we want to convey information to the client of the plugin. The errorHandler function in Main.js will be called. In this case the CSBooking instance will not be stored inside the event.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user.
More information in the Error Handling section.
F.2.3. _modify(self, oldBookingParams)
The _modify(self, oldBookingParams) method this will be called by the core when the user is editing a booking or modifying a request. When this method is called, the following has already happened:
- A copy of the old booking parameters has been made by calling the method getBookingParams of the CSBookingBase class and stored in a dictionary with the same format as the one that is sent to the client.
- The sanitizeParams method of the CSBookingBase class has been called and the sanitization check was successful (see /wiki/Dev/Technical/CollaborationPluginsDevelopment#I.4 Parameter sanitization].
- All the new booking parameters values (simple and complex) have been set, overwriting the old values.
- The self._checkBookingParams method has been called for the new values and the verification was successful.
Therefore, the oldBookingParams parameter of the _modify method will contain the previously mentioned dictionary with the booking parameter values before they were modified. This is useful if we want to not call the remote API if no parameter values have changed, for example. These operations are performed by the changeBooking method of the core class CSBookingManager.
In the _modify method you should implement whatever needs to be done when a booking / request is modifiedin Indico, such as:
- Calling an operation of the remote system API,
- Setting attribute values, such as information returned by the remote operation, or the self._statusMessage and self._statusClass attributes (see Special CSBooking instance attributes.
- Sending notification emails (to the event creator, the plugin admin...).
The _modify method should return:
- None if there have been no problems. In this case, the core will fossilize the new booking / request and return it to the client.
- an instance of CSErrorBase if there was a problem and we want to convey information to the client of the plugin. The errorHandler function in Main.js will be called. In this case, the core will automatically restore the old booking parameters by calling the method setBookingParams of the CSBookingBase class with the oldBookingParams. However, any changes you have done to other instance attributes will remain.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will not be a commit to the database and the old booking parameters will remain in the database even if they were already set in memory, and changes done to other attributes will disappear.
More information in the Error Handling section.
F.2.4. _delete(self)
The _delete(self) method this will be called by the core when the user is deleting a booking or withdrawing a request. The "delete" button for a booking (a small red cross) will only be clickable if the self._canBeDeleted instance attribute of the booking is True (which it is by default). This operation has no parameters or parameter verification. It is called by the removeBooking method of the core CSBookingManager class.
In the _delete method you should implement whatever needs to be done when a booking is deleted or a request is withdrawn, such as:
- Calling an operation of the remote system API,
- Sending notification emails (to the event creator, the plugin admin...).
The _delete method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request and return it to the client (useful for the postDelete function in Main.js).
- an instance of CSErrorBase if there was a problem and we want to convey information to the client of the plugin. The errorHandler function in Main.js will be called. In this case, the CSBooking instance will not be removed from the list of bookings / requests of the event, but whatever attribute changes you have done (such as setting self._warning) will remain.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.2.5. _checkStatus(self)
The _checkStatus(self) method this will be called by the core when the user presses the "update status" button, which only exists if the _hasCheckStatus class attribute of the CSBooking class is set to True (see Plugin characteristics). This operation has no parameters or parameter verification. It is called by the checkBookingStatus method of the core CSBookingManager class.
In the _checkStatus method you should query the remote system to see if the booking was changed in the remote system.
The _checkStatus method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request (including the changed attributes) and return it to the client. The core will refresh the information in the client and the plugin can perform any extra needed operation in the "postCheckStatus" function of Main.js.
- an instance of CSErrorBase if there was a problem and we want to convey information to the client of the plugin. The errorHandler function in Main.js will be called. In this case, what will be fossilized and returned to the client is the error instance, not the booking; therefore the booking / request will not be refreshed.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.2.6. _notifyOnView(self)
The _notifyOnView(self) method this will be called by the core for every booking / request when the event manager goes to the Video Services section of the event, or a normal user sees bookings in an event display page. This happens before the bookings are rendered in the page. This only happens if the _needsToBeNotifiedOnView class attribute of the CSBooking class is set to True (see Plugin characteristics). This operation has no parameters or parameter verification.
In the _notifyOnView method you can recalculate some of the instance attributes (for example, if the booking can be started or not, depending on its start time and the current time), and optionally also query the remote system. For example, the EVO plugin queries the remote system when the clock is close to the booking starting time, but stores that this check is done in order not to do it repeatedly if the page is refreshed.
The _notifyOnView method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request (including the changed attributes) and return it to the client.
- raise an Exception if there has been a problem. In this case, a message will be written in the Indico log but nothing will be reported to the user to avoid plugins spamming the users when they are just viewing a page.
F.2.7. _start(self)
The _start(self) method this will be called by the core when an event manager presses the "Start" button for a booking. Normally, only Booking plugins need this operation. This operation has no parameters or parameter verification. It is called by the startBooking method of the core CSBookingManager class.
The "Start" button only appears if the _hasStart class attribute is set to True, and the _start() method of the CSBooking instance is only called if _requiresServerCallForStart class attribute is also set to True (see Plugin characteristics).
In the _start method you should implement whatever needs to be done when a booking is started, such as:
- Calling an operation of the remote system API,
- Sending notification emails (to the event creator, the plugin admin...),
- Changing the status message of the booking.
The _start method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request and return it to the client; the core will refresh it in the client automatically, so that for example the new status message is displayed.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.2.8. _stop(self)
The _stop(self) method this will be called by the core when an event manager presses the "Stop" button for a booking. Normally, only Booking plugins need this operation. This operation has no parameters or parameter verification. It is called by the stopBooking method of the core CSBookingManager class.
The "Stop" button only appears if the _hasStop class attribute is set to True, and the _stop() method of the CSBooking instance is only called if _requiresServerCallForStop class attribute is also set to True (see Plugin characteristics).
In the _stop method you should implement whatever needs to be done when a booking is stopped, such as:
- Calling an operation of the remote system API,
- Sending notification emails (to the event creator, the plugin admin...),
- Changing the status message of the booking.
The _stop method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request and return it to the client; the core will refresh it in the client automatically, so that for example the new status message is displayed.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.2.9. _accept(self)
The _accept(self) method this will be called by the core when an plugin admin presses the "Accept" button for a booking or request. Normally, only Request plugins need this operation, but we can also imagine a system where booking need to be accepted by admins. This operation has no parameters or parameter verification. It is called by the accept method of the core CSBookingBase class, in turn called by the acceptBooking method of the core CSBookingManager class.
The "Accept" button only appears if the _hasAcceptReject class attribute is set to True (see Plugin characteristics). It will only be clickable if the instance attribute _canBeStarted is True (by default, it has the value of the _hasStart class attribute).
Before the _accept method of the CSBooking class is called, the accept method of the core CSBookingBase class already changes the instance attribute _acceptRejectStatus to True, so you do not need to keep track if a booking / request has been accepted or rejected.
In the _accept method you should implement whatever needs to be done when a booking is accepted, such as:
- Calling an operation of the remote system API,
- Sending notification emails (to the event creator, the plugin admin...),
- Changing the status message of the booking and instance attributes such as _canBeStarted and _canBeStopped.
The _accept method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request and return it to the client; the core will refresh it in the client automatically, so that for example the new status message is displayed.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.2.10. _reject(self)
The _reject(self) method this will be called by the core when an plugin admin presses the "Reject" button for a booking or request, and writes the rejection reason. Normally, only Request plugins need this operation, but we can also imagine a system where booking need to be accepted by admins. This operation has no parameters or parameter verification. It is called by the reject method of the core CSBookingBase class, in turn called by the rejectBooking method of the core CSBookingManager class.
The "Reject" button only appears if the _hasAcceptReject class attribute is set to True (see Plugin characteristics). It will only be clickable if the instance attribute _canBeStopped is True (by default, it has the value of the _hasStart class attribute).
Before the _reject method of the CSBooking class is called, the reject method of the core CSBookingBase class already changes the instance attribute _acceptRejectStatus to False, and stores the rejection reason in the _rejectReason instance attribute.
In the _reject method you should implement whatever needs to be done when a booking is accepted, such as:
- Calling an operation of the remote system API,
- Sending notification emails (to the event creator, the plugin admin...),
- Changing the status message of the booking and instance attributes such as _canBeStarted and _canBeStopped.
The _reject method should return:
- None if there have been no problems. In this case, the core will fossilize the booking / request and return it to the client; the core will refresh it in the client automatically, so that for example the new status message and rejection reason are displayed.
- raise an instance of CollaborationException if there has been an "uncontrolled" problem. An error report dialog will be shown to the user. In this case, there will be no database commit and no changes will be stored.
More information in the Error Handling section.
F.3. Special CSBooking instance attributes
The following instance attributes are initialized in the CSBookingBase base class and are use by the core for different purposes:
- self._bookingParams: as mentioned in the previous sections, this is a dictionary where the Simple booking parameters are stored and retrieved by the core. The CSBookingBase base class initializes it to an empty dictionary, so your plugin does not need to do so. To retrieve a value from your plugin, the safest way is to use the getBookingParamByName method of the CSBookingBase base class.
- self._id: this is the internal per-event id of the booking / request. Your plugin should not change this but it may be interesting for your plugin to retrieve its value with the getId() method of the CSBookingBase base class.
- self._statusMessage and self._statusClass: this is the text shown in the Status column for Booking Plugins or at the top of the form for Request plugins. _statusMessage is the text that will be displayed and _statusClass is its CSS class, for which you should use: "statusMessageOK", "statusMessageError" and "statusMessageOther". It is interesting to set this attribute when a booking / request is created, modified, started, stopped, accepted, rejected, etc. You can retrieve their values with the getStatusMessage and getStatusClass method of the CSBookingBase base class.
- self._acceptRejectStatus and self._rejectReason: only relevant if _hasAcceptReject is True (see Plugin characteristics). These instance attributes are set by the core when a plugin admin presses the "Accept" or "Reject" buttons, as explained in the _accept and _reject method descriptions. You can recover their values with the getAcceptRejectStatus and getRejectReason methods of the CSBookingBase base class. _acceptRejectStatus will be None if the request has not been accepted or rejected yet, True if it has been accepted, and False if it has been rejected.
- self._canBeStarted and self._canBeStopped: only relevant if _hasStart and / or _hasStop are True (see Plugin characteristics). These attributes will tell the core if the "Start" and "Stop" buttons for a booking should be enabled. By default, _canBeStarted starts with the same value as the _hasStart class attribute, and _canBeStopped starts being False.
- self._permissionToStart and self._permissionToStop: only relevant if _hasStart and / or _hasStop are True, and _requiresClientCallForStart and / or _requiresClientCallForStop are also True (see Plugin characteristics). They will tell the core if it should call the functions "start" and "stop" in Main.js. This is useful, for example, if we want to check in the server if a booking may still be started or stopped. For example, we may be after its ending time, but the page in the client was loaded some time ago and the "Start" button is still active.
- self._warning: any primitive value (string, etc.) or Fossilizable object can be put here. It will be fossilized along with the booking parameters. Then, it can be recovered in the Main.js functions which are called after a server operaton, such as postCreate(), postDelete(), etc. See the Javascript interface between core and plugins section.
- self._creationDate and self._modificationDate: Python timezone-aware objects, set by the core. Do not change them with your plugin, but they can be read via the getCreationDate and getModificationDate methods of the CSBookingBase base class. self._creationDate is set when the _create method of CSBooking is called, and self._modificationDate is set every time the _create or _modify methods of CSBooking are called.
- self._canBeDeleted: this attribute is used by the core to check if it should disable the "Delete" button for a booking, therefore turning the small red cross icon into a non-clickable grey cross. For example, EVO does not allow to delete ongoing or past meetings, so the EVO plugin disables the "Delete" button in these cases.
- self._needsToBeNotifiedOfDateChanges: this attribute is used by the core to enable or disable the "Keep synchronized with event" checkbox in the "Advanced" tab. For example, it may not make sense to keep synchronizing a booking and an event which are both past.
- self._hidden: this attribute is set by the core when the user checks or unchecks the "Keep this booking hidden" checkbox of the Advanced tab. Its value is True or False.
F.4. Fossils
In order to turn Python objects into Javascript objects, the Collaboration core (and Indico in general) follows a two-step process:
- Turn the Python object of a custom class into a simple object made only of Python dicts, lists, strings and integers;
- Turn the previous object into a JSON string.
The second step is accomplished thanks to the simplejson library.
The first step, however, is performed by Indico. Initially, this was done thanks the the Indico DictPickler module, by calling DictPickler.pickle() and writing @Retrieves decorators before methods that should be used to pickle the object.
For performance and reusability reasons, however, this system has been changed to use Indico's fossilize module. Classes that wish to be pickled now have to inherit from the Fossilizable base class, and the programmer has to declare one or several Fossil interfaces. (documentation for the fossilize module should be available soon, meanwhile check the doc/api/source/fossilize.rst file).
By default, the core uses the classes in MaKaC/plugins/Collaboration/fossils.py to fossilize CSBooking instances (and also for CSErrorBase instances). For CSBooking instances, two different fossils are used: ICSBookingBaseConfModifFossil when the booking / request is rendered in event management pages, and ICSBookingBaseIndexingFossil when the booking is rendered in the Video Services Overview page.
ICSBookingBaseConfModifFossil calls the getBookingParams method, so the booking parameters are available in Javascript as second-level attributes under the .bookingParams attribute. Other CSBooking attributes used by the core, such as id, statusMessage, etc. are available as first-level attributes. However plugin-defined internal instances attributes are not available by default since the core cannot know about them.
If you have defined some internal instance attributes that you need to use in your Javascript code, here is what you should do:
- create a fossils.py file in your plugin and create there a class called ICSBookingConfModifFossil that inherits from ICSBookingBaseConfModifFossil. Add the methods that you wish the core to call in order to fossilize your booking / request object.
- add the line fossilizes(ICSBookingConfModifFossil) under your "class CSBooking(CSBookingBase):" line in collaboration.py (and the necessary imports).
Another thing you probably want to do is also create a subclass of ICSBookingBaseIndexingFossil in order to remove from the fossil sensitive information such as user passwords, since the Video Services Overview page is not HTTPs. In this case, you should do the following:
- inside the fossils.py file, create a class called ICSBookingIndexingFossil (the file name and the class name are compulsory so that the core can find it) that extends ICSBookingBaseIndexingFossil.
- add the fossil class to the fossilizes line mentioned previously (but leave CSBookingConfModifFossil as the first fossil in the list).
- add any methods that are needed, or remove booking parameters from those sent to the client by doing something similar to:
def removeAccessPassword(bookingParams): del bookingParams["accessPassword"] return bookingParams class ICSBookingIndexingFossil(ICSBookingBaseIndexingFossil): def getBookingParams(self): """ Overloading of ICSBookingBaseFossil's getBookingParams to remove the access password """ getBookingParams.convert = lambda bookingParams: removeAccessPassword(bookingParams)
Note: if you only implement a ICSBookingIndexingFossil but no ICSBookingConfModifFossil, you still have to put ICSBookingBaseConfModifFossil as first fossil in the "fossilizes" list in the CSBooking class.
Finally, if you have subclassed CSErrorBase (to make an EVOError class, for example) you probably also want to make a custom fossil that inherits from ICSErrorBaseFossil.
Please note that you can write fossils for the objects used internally for your plugin too! Just remember to have their classes inherit from Fossilize, add the fossilize(fossilName) line to your class, and create a fossil interface somewhere in your plugin (fossils.py for example, but this is not compulsory).
F.5. E-mails
F.6. Custom services in the server
In this section we will discuss how to implement a service in the server. The call of the service from the client is discussed in the section E.5. Calling services.
In the server, Service classes who take AJAX requests from the client are located in the plugin's services.py file.
Each of your services has to be a class similar to this one:
from MaKaC.services.implementation.collaboration import CollaborationPluginServiceBase class XXXXXService(CollaborationPluginServiceBase): def _checkParams(self): # parameter parsing / treatment here. raise exception if invalid parameters def _getAnswer(self): # answer building here. a JSON serializable object, such as a primitive type, or a list of strings, or a fossil, should be returned
XXXXX is the name of the service as you wrote it in the service call made in Javascript (see section E.5. Calling Services), in camelcase with initial capital letter. You can see examples in the RecordingRequest and WebcastRequest plugins.
Inheritance from CollaborationPluginServiceBase is compulsory. It gives you these benefits:
- You don't need to implement _checkProtection like for a normal service. Your service will be called by the CollaborationCustomPluginService service, which will take care of making sure that only people with proper permissions can perform your operation (only admins if the plugin is an admin service, or only managers otherwise). However you can implement the method if you like to perform additional checks.
- You already have the following attributes available:
- self._params : like any Indico service, this is a dictionary with the call parameters, i.e. the parameters you passed in your second argument of the indicoRequest javascript AJAX call.
- self._aw: an AccessWrapper object with a getUser() method that will return the logged in user as an Avatar instance.
- self._conf: the Conference object corresponding to the Indico Event we are working with (this is provided by CollaborationBase which CollaborationPluginServiceBase inherits from).
- self._CSBookingManager: the CSBookingManager object attached to the Event (this is provided by CollaborationBase which CollaborationPluginServiceBase inherits from).
_getAnswer() will be always called after _checkParams(). If you do implement _checkProtection, it will be called between _getAnswer() and _checkParams().
Normally in _checkParams() you verify the parameters and build the objects you will work with in _getAnswer(), putting them in self. Then in _getAnswer() you build the asnwer and return a JSON serializable object, i.e. a primitive type, or a list of strings, or a fossil / pickled object. This is no different from normal Indico services.
F.7. Beware of ConflictError! Encapsulate your external operations
As you probably know, Indico uses ZODB, an object-oriented database, for persistance. ZODB supports ACID transactions. When two transactions modify the same object and call commit(), the followind happens:
- The first transaction succeeds and the changes are written to the database.
- The second transaction fails and a ConflictError exception is raised.
When this happens, Indico captures the ConflictError exception, re-loads the updated objects into memory, and executes the request code again.
In practice, this means that the code inside your _create(), _modify(), etc. methods and your custom services can be executed more than once. This is not a problem for the code lines that modify the database, but it is a problem for those who perform "external" operations, such as:
- Calling an external system API operation,
- Sending an email,
- Creating a file or writing to a file in the server's filesystem.
Ideally, emails should be sent after the transaction has been comitted (so that they are only sent once), but the mechanism so that plugins can do this easily is not yet in place. Moreover, calling external API operations is something that plugins often need to do before the database commit (because these operations return values that have to be persisted in the Indico database).
The solution is to program following these advices:
- Encapsulate your external operations (specially external API operations) as much as possible, in a single function call if possible.
- Use the ExternalOperationsManager class (in MaKaC/common/externalOperationsManager.py) to call those functions. The ExternalOperationsManager will make sure that your operation is only called once, caching the result for future retries. Example:
def _create(self): result = ExternalOperationsManager.execute(self, "createBooking", EVOOperations.createBooking, arg1, arg2, arg3)
G. Other programming
G.1. Display pages
G.1.1. For events of type conference
G.1.2. For events of lecture and meeting
G.2. Error handling
G.2.1. Parameter checking in the client
As mentioned in section E.3, one of the functions that you can implement in Main.js is the checkParams() function. This function will be called by the core when the user presses the Save button in a booking creation / edition dialog, or the Send Request button in a request form.
The purpose of this function is to perform checks in Javascript to the booking parameters, so that if something is wrong, the corresponding fields are highlighted, or an error is shown to the user, before the information is sent to the server.
The checkParams() function has to return a dictionary where:
- The key is the name attribute of the field that is being checked,
- The value is a 3-tuple (an array of 3 items) consisting of:
- The type of value in the field being verified, as a string. Supported types are: "text", "int", ... (to be completed).
- A boolean that should be true if we want to allow the field to be empty, or false otherwise.
- (optional) a custom check function, that will be called by the core if declared. This function will receive two arguments: the value in the field being checked and a dictionary with the values of the other fields. It has to return a list of strings that will be displayed as a tooltip if the field is in an error state, or an empry list if the field is in a correct state.
Let's see an example of the checkParams() function:
checkParams: function() { return { 'pin': ['non_negative_int', true, function(pin, otherValues) { var errors = []; if (pin.length >= 32) { errors.push($T("The pin cannot have more than 31 characters.")); } return errors; }] }; }
With this function, we are verifying that the contents of the 'pin' field are a non negative integer, we allow it being empty, and we verify that its length is 31 characters or less.
G.2.2. Raise Exceptions for unexpected events
In the server side, you should raise Exceptions when unexpected or anomalous events occur. Examples:
- You have a 'pin' field in your dialog / form. The value that arrived to the server is not a number, even if you also verified this in the client.
- In your CSBooking class's _create() method, connection to a remote system is not possible because Indico's credentials were not properly configured by the Indico admins.
In cases such as this, Indico shows an error report dialog to the user. The user can click "Send Report" and an email will be sent to the Indico admins.
For this, you should implement your own exception class that inherits from CollaborationException and raise it in your methods. A good place to place your own Exception class is the common.py file.
G.2.3. Controlled errors
In the server side, your CSBooking class's methods should return Errors in situations such as these:
- Indico tried to create a booking in the remote system, but the remote system refuses to do it for a specific reason, for example: the booking is considered duplicated because it has the same name / title as an existing booking in the remote system. Therefore, the name / title field of the booking dialog should be highlighted and a tooltip saying "Please change the name, it is considered duplicated" should be shown.
- Some of the booking / requests parameters are not right and the verification could only be done in the server.
In these cases, you want the server to return information with the cause of the error, and you want the client part of your plugin to get this information and do something with it.
For this, you should implement a class that inherits from CSErrorBase. Return an instance of this class in methods such as _create(), _modify(), delete(), etc. which normally do not have to return anything. When you do this, the booking information will not actually be stored in the Indico database (the booking will not be created / modified / deleted).
Your error will be turned into a JSON object that will be passed to the function errorHandler(event, error) of the Main.js file. The event argument will be a string detailing which action triggered the error: "create", "edit", "checkStatus", "delete", etc. The error argument will be the JSON object representing your error.
Example: we have declared an ExampleError class:
class ExampleError(CSErrorBase): def __init__(self, errorType): self._errorType = errorType @Retrieves([...], 'errorType') #or make a fossil if you are working with a version of the core that uses fossilizing def getErrorType(self): return self._errorType
Our errorHandler(event, error) function in Main.js would look like this:
errorHandler: function(event, error) { if (event === "create" && error.errorType === "duplicated") { var message = $T("The remote system considers that name duplicated"); IndicoUtil.markInvalidField($E('name'), message); } }
To display errors, you can use these helper functions:
- IndicoUtil.markInvalidField(XElement, message): will mark a field in red and place a tooltip with a message when the mouse is hovered over the field. Changing the contents of the field will remove the red color and remove the tooltip too.
- CSErrorPopup(title, array_of_lines): will display a popup similar to an "alert()" with the given title and a list of messages.
G.2.4. Warnings
In some cases, you want an operation to succeed but you still want to show a warning to the user.
If you do this via the Error / errorHandler mechanism, you could display such a warning by parsing the error JSON object but the operation would not succeed in the server.
In all operations that succeed, the CSBooking object is turned into a JSON object and sent back to the client. This is how a booking can appear in the list of bookings after being created, for example.
You can assign a value to the attribute _warning of the CSBooking class and it will be included in the JSON object. Then, if you implement methods such as postDelete(booking), postCreate(booking), etc. you can parse that information and do something with it.
For example, let's say that our plugin wants to take into account this situation: the user deletes a booking but this booking was already deleted in the remote system (maybe an admin of that system already deleted it). In this case, it would make no sense to not delete the booking also in Indico, but we still want to show a message to the user. In our CSBooking class's delete() method we do:
def _delete(self): ... if ( ... ): self._warning = "deletedNonExistant"
And in our Main.js file we implement the postDelete() function:
postDelete: function(booking) { if (booking.warning && booking.warning === "deletedNonExistant") { alert($T("Your booking was deleted successfully but please note that had already been deleted in the remote system."); // You can also use the AlertPopup class or the IndicoUI.Dialogs.Util.alert if available } }
H. Utility functions available
H.1. CollaborationTools class
H.2. MailTools class
H.3. Javascript helper functions
I. Other tasks performed by the core
I.1. Indexation
I.2. Time synchronization
I.3. Hidden bookings
I.4. Parameter sanitization
Attachments (3)
-
Booking list.png
(34.0 KB) -
added by dmartinc 6 years ago.
Booking list screenshot
-
EVO creation dialog.png
(25.8 KB) -
added by dmartinc 6 years ago.
EVO creation dialog screenshot
-
Recording Request Form.png
(45.1 KB) -
added by dmartinc 6 years ago.
Recording Request form screenshot
Download all attachments as: .zip