Quite often, in a given listing (e.g. list of Samples) one would want to display more columns, add a new filter at the top, filter items differently, or even add a new “custom” Add button.
This recipe explains how to customize and/or change the behavior of listings by using your own add-on, without the need of modifying senaite’s code base.
Listings can be customized by using subscriber adapters. This functionality was introduced in https://github.com/senaite/senaite.core.listing/pull/2. A basic use-case is explained in that same Pull Request. Also, a discussion about this topic can be found in Gitter archives: https://gitter.im/senaite/Lobby?at=5d3b209dca086f6739e1c38e
In this recipe, we will explain how to add a new column in samples listing first and how to add a new “status” filter button at the top of the list.
Creation of the adapter and registration
First, you need to create the adapter class in your add-on:
from bika.lims import api
from senaite.core.listing.interfaces import IListingView
from senaite.core.listing.interfaces import IListingViewAdapter
class MyOwnSamplesListingAdapter(object):
adapts(IListingView)
implements(IListingViewAdapter)
# Order of priority of this subscriber adapter over others
priority_order = 1000
def __init__(self, listing, context):
self.listing = listing
self.context = context
def before_render(self):
# Do your own stuff here. E.g., add search criteria in `contentFilter` variable
return
def folder_item(self, obj, item, index):
# Do your own stuff here. E.g., set the value to display for a given column
return item
Note this class adapts IListingView
in first place, which means that this adapter will “adapt” (customize, change the behavior of) objects from IListingView
type, the type that all senaite listings implement. This adapter implements IListingViewAdapter
. While rendering any listing, senaite will look for existing adapters that implement this type for the given listing. If an adapter is found, the functions before_render
and folder_item
will be called accordingly during the listing rendering life-cycle.
At this point, we’ve created the basic adapter, but we haven’t yet registered the adapter. Register the adapter by adding the following snippet in your configure.zcml
file (located in same package where you added the python class), or create a new one:
<!-- Samples listing with additional filters and columns -->
<subscriber
for="bika.lims.browser.analysisrequest.AnalysisRequestsView
bika.lims.interfaces.IAnalysisRequestsFolder"
provides="senaite.core.listing.interfaces.IListingViewAdapter"
factory=".MyOwnSamplesListingAdapter" />
Note the following parameters:
-
for : we tell the system which is the listing that needs to be adapted (first line) and in which context this listing has to be adapted. In this case, we tell the system that we want to adapt the samples listing (aka analysis requests), but only when the context is the main listing folder. If we wanted the adapter to work for all listings regardless of the context we could replace
bika.lims.interfaces.IAnalysisRequestsFolder
by*
. - provides: The type of adapter provided
- factory: relative or full canonical path to your new adapter class
At this point, your adapter will be called each time the main samples listing is called. Now is time to make the adapter do what we want:
Add a new status filter button and column in your listing
As you’ve noticed, there is a function in the adapter called before_render
. As the name states, this function is called just before the listing gets rendered. Thus, is the right place to apply wide-listing modifications, such as adding new columns, modifying the search criteria, adding new filter buttons, etc. In our example, we will add a new filter button and a new column. Add the following code inside before_render
:
def before_render(self):
# Add a new filter status
draft_status = {
"id": "draft",
"title": "Draft",
"contentFilter": {
"review_state": "sample_draft",
"sort_on": "created",
"sort_order": "descending",
},
"columns": self.listing.columns.keys(),
}
self.listing.review_states.append(draft_status)
# Add the column
self.listing.columns["MyColumn"] = {
"title": "My column",
"sortable": False,
"toggle": True,
}
# Make the new column visible for all filter statuses
for filter in self.listing.review_states:
filter.update({"columns": self.listing.columns.keys()})
Next step is to “display” the value in the listing while rendering a given item (in this case, a sample). Add the following code in folder_item
function:
def folder_item(self, obj, item, index):
sample = api.get_object(obj)
item["MyColumn"] = obj.getField("MyCustomAttribute").get(sample) or "Empty value"
return item
Note that here we’ve assumed that you added a new Schema field “MyCustomAttribute” for the content type “AnalysisRequest”.
Further reading: