Please check your agent type and version and read on or switch to the right documentation.
Create and manage Smart Actions
What is a Smart Action?
Sooner or later, you will need to perform actions on your data that are specific to your business. Moderating comments, generating an invoice, logging into a customer’s account or banning a user are exactly the kind of important tasks to unlock in order to manage your day-to-day operations.
On our Live Demo example, our companies collection has many examples of Smart Action. The simplest one is Mark as live.
If you're looking for information on native actions (CRUD), check out this page.
Creating a Smart action
In order to create a Smart action, you will first need to declare it in your code for a specific collection. Here we declare a Mark as Live Smart action for the companies collection.
classForest::CompanyincludeForestLiana::Collection collection :Company action 'Mark as Live'end
app/forest/companies.py
from django_forest.utils.collection import Collectionfrom app.models import CompanyclassCompanyForest(Collection):defload(self): self.actions = [{'name':'Mark as Live'}]Collection.register(CompanyForest, Company)
Ensure the file app/forest/__init__.py exists and contains the import of the previous defined class :
app/forest/__init__.py
from app.forest.companies import CompanyForest
app/Models/Company.php
<?phpnamespaceApp\Models;useForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;useForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartAction;useIlluminate\Database\Eloquent\Factories\HasFactory;useIlluminate\Database\Eloquent\Model;/** * Class Company */classCompanyextendsModel{useHasFactory;useForestCollection;/** * @returnSmartAction */publicfunctionmarkAsLive():SmartAction {return$this->smartAction('single','Mark as Live'); }}
After declaring it, your Smart action will appear in the Smart actions tab within your collection settings.
A Smart action is displayed in the UI only if:
it is set as "visible" (see screenshot below)
AND
in non-development environments, the user's role must grant the "trigger" permission
You must make the action visible there if you wish users to be able to see it.
It will then show in the actions dropdown button:
At this point, the Smart Action does nothing, because no route in your Admin backend handles the API call yet.
The Smart Action behavior is implemented separately from the declaration.
In the following example, we've implemented the Mark as live Smart Action, which simply changes a company's status to live.
You must make sure that all your Smart Actions routes are configured with the Smart Action middleware: permissionMiddlewareCreator.smartAction(). This is mandatory to ensure that all features built on top of Smart Actions work as expected (permissions, approval workflows,...).
You must make sure that all your Smart Actions routes are configured with the Smart Action middleware: permissionMiddlewareCreator.smartAction(). This is mandatory to ensure that all features built on top of Smart Actions work as expected (permissions, approval workflows,...).
The route declaration takes place in config/routes.rb.
/config/routes.rb
Rails.application.routes.draw do # MUST be declared before the mount ForestLiana::Engine. namespace :forest do post '/actions/mark-as-live'=>'companies#mark_as_live' end mount ForestLiana::Engine =>'/forest'end
The business logic in this Smart Action is extremely simple. We only update here the attribute status of the companies to the value live:
/app/controllers/forest/companies_controller.rb
classForest::CompaniesController<ForestLiana::SmartActionsControllerdefmark_as_live company_id =ForestLiana::ResourcesGetter.get_ids_from_request(params, forest_user).firstCompany.update(company_id, status: 'live') head :no_contentendend
You must make sure that all your Smart Actions controllers extend from the ForestLiana::SmartActionsController. This is mandatory to ensure that all features built on top of Smart Actions work as expected (authentication, permissions, approval workflows,...)
You may have to add CORS headers to enable the domain app.forestadmin.com to trigger API call on your Application URL, which is on a different domain name (e.g. localhost:3000).
Make sure your projecturls.py file include you app urls with the forest prefix.
The business logic in this Smart Action is extremely simple. We only update here the attribute status of the companies to the value live:
app/views.py
from django.http import JsonResponsefrom django_forest.utils.views.action import ActionViewclassMarkAsLiveView(ActionView):defpost(self,request,*args,**kwargs): params = request.GET.dict() body = self.get_body(request.body) ids = self.get_ids_from_request(request, self.Model)returnJsonResponse({'success': 'live!'})
Note that Forest Admin takes care of the authentication thanks to the ActionView parent class view.
You may have to add CORS headers to enable the domain app.forestadmin.com to trigger API call on your Application URL, which is on a different domain name (e.g. localhost:8000).
The route declaration takes place in routes/web.php.
When you trigger the Smart Action from the UI, your browser will make an API call: POST /forest/actions/mark-as-live.
If you want to customize the API call, check the list of available options.
The payload of the HTTP request is based on a JSON API document.
The data.attributes.ids key allows you to retrieve easily the selected records from the UI.
The data.attributes.values key contains all the values of your input fields (handling input values).
Other properties of data.attributes are used to manage the select all behavior.
Should you want not to use the RecordsGetter and use request attributes directly instead, be very careful about edge cases (related data view, etc).
Available Smart Action options
Here is the list of available options to customize your Smart Action:
Name
Type
Description
name
string
Label of the action displayed in Forest Admin.
type
string
fields
array of objects
download
boolean
(optional) If true, the action triggers a file download in the Browser. Default is false
endpoint
string
(optional) Set the API route to call when clicking on the Smart Action. Default is '/forest/actions/name-of-the-action-dasherized'
httpMethod
string
(optional) Set the HTTP method to use when clicking on the Smart Action. Default is POST.
description
string
(optional) Add a description shown in the smart action form. This supports html tags. ⚠️ only available in forest-express-sequelize and forest-express-mongoose9.4.0
submitButtonLabel
string
(optional) Sets the text written on the submit button at the end of the form. Default value is the Smart Action name. ⚠️ only available in forest-express-sequelize and forest-express-mongoose9.4.0
Name
Type
Description
name
string
Label of the action displayed in Forest Admin.
type
string
fields
array of objects
download
boolean
(optional) If true, the action triggers a file download in the Browser. Default is false
endpoint
string
(optional) Set the API route to call when clicking on the Smart Action. Default is '/forest/actions/name-of-the-action-dasherized'
http_method
string
(optional) Set the HTTP method to use when clicking on the Smart Action. Default is POST.
description
string
(optional) Add a description shown in the smart action form. This supports html tags. ⚠️ only available in forest_liana9.4.0
submit_button_label
string
(optional) Sets the text written on the submit button at the end of the form. Default value is the Smart Action name. ⚠️ only available in forest_liana9.4.0
Want to go further with Smart Actions? Read the next page to discover how to make your Smart Actions even more powerful with Forms!
Available Smart Action properties
req.user
The JWT Data Token contains all the details of the requesting user. On any authenticated request to your Admin Backend, you can access them with the variable req.user.
You can find important information in the body of the request.
This is particularly useful to find the context in which an action was performed via a relationship.
{ data: { attributes: { collection_name:'users',//collection on which the action has been triggered values: {}, ids: [Array],//IDs of selected records parent_collection_name:'companies',//Parent collection name parent_collection_id:'1',//Parent collection id parent_association_name:'users',//Name of the association all_records:false, all_records_subset_query: {}, all_records_ids_excluded: [], smart_action_id:'users-reset-password' }, type:'custom-action-requests' }}
Customizing response
Default success notification
Returning a 204 status code to the HTTP request of the Smart Action shows the default notification message in the browser.
On our Live Demo example, if our Smart Action Mark as Live route is implemented like this:
If we return a 200 status code with an object { success: '...' } as the payload like this…
/routes/companies.js
...router.post('/actions/mark-as-live',permissionMiddlewareCreator.smartAction(), (req, res) => {// ...res.send({ success:'Company is now live!' });});...
/routes/companies.js
...router.post('/actions/mark-as-live',permissionMiddlewareCreator.smartAction(), (req, res) => {// ...res.send({ success:'Company is now live!' });});...
classForest::CompaniesController<ForestLiana::SmartActionsControllerdefmark_as_live# ... render json: { success: 'Company is now live!' }endend
app/views.py
from django.http import JsonResponsefrom django_forest.utils.views.action import ActionViewclassMarkAsLiveView(ActionView):defpost(self,request,*args,**kwargs):returnJsonResponse({'success': 'Company is now live!'})
app/Http/Controllers/CompaniesController.php
classCompaniesControllerextendsForestController{/** * @returnJsonResponse */publicfunctionmarkAsLive():JsonResponse {# ....returnresponse()->json(['success'=>"Company is now live !"]); }}
… the success notification will look like this:
Custom error notification
Finally, returning a 400 status code allows you to return errors properly.
/routes/companies.js
...router.post('/actions/mark-as-live',permissionMiddlewareCreator.smartAction(), (req, res) => {// ...res.status(400).send({ error:'The company was already live!' });});...
/routes/companies.js
...router.post('/actions/mark-as-live',permissionMiddlewareCreator.smartAction(), (req, res) => {// ...res.status(400).send({ error:'The company was already live!' });});...
/app/controllers/forest/companies_controller.rb
classForest::CompaniesController<ForestLiana::SmartActionsControllerdefmark_as_live# ... render status: 400, json: { error: 'The company was already live!' }endend
app/views.py
from django.http import JsonResponsefrom django_forest.utils.views.action import ActionViewclassMarkAsLiveView(ActionView):defpost(self,request,*args,**kwargs):returnJsonResponse({'error': 'The company was already live!'}, status=400)
app/Http/Controllers/CompaniesController.php
classCompaniesControllerextendsForestController{/** * @returnJsonResponse */publicfunctionmarkAsLive():JsonResponse {# ....returnresponse()->json(['error'=>"The company was already live!"],400); }}
Custom HTML response
You can also return a HTML page as a response to give more feedback to the admin user who has triggered your Smart Action. To do this, you just need to return a 200 status code with an object { html: '...' }.
On our Live Demo example, we’ve created a Charge credit card Smart Action on the Collection customersthat returns a custom HTML response.
/forest/companies.js
const { collection } =require('forest-express-sequelize');collection('customers', { actions: [ { name:'Charge credit card', type:'single', fields: [ { field:'amount', isRequired:true, description:'The amount (USD) to charge the credit card. Example: 42.50', type:'Number', }, { field:'description', isRequired:true, description:'Explain the reason why you want to charge manually the customer here', type:'String', }, ], }, ],});
classForest::CustomerincludeForestLiana::Collection collection :Customer action 'Charge credit card', type: 'single', fields: [{ field: 'amount', is_required: true, description: 'The amount (USD) to charge the credit card. Example: 42.50', type: 'Number' }, { field: 'description', is_required: true, description: 'Explain the reason why you want to charge manually the customer here', type: 'String' }]end
/config/routes.rb
Rails.application.routes.draw do# MUST be declared before the mount ForestLiana::Engine. namespace :forest do post '/actions/charge-credit-card'=>'customers#charge_credit_card'end mount ForestLiana::Engine=>'/forest'end
from django_forest.utils.collection import Collectionfrom app.models import CompanyclassCompanyForest(Collection):defload(self): self.actions = [{'name':'Charge credit card','fields': [{'field':'amount','description':'The amount (USD) to charge the credit card. Example: 42.50','isRequired':True,'type':'Number'},{'field':'description','description':'Explain the reason why you want to charge manually the customer here','isRequired':True,'type':'String'}, ],}]Collection.register(CompanyForest, Company)
You can either respond with an HTML page in case of error. The user will be able to go back to his smart action's form by using the cross icon at the top right of the panel.
/forest/companies.js
const { collection } =require('forest-express-sequelize');collection('customers', { actions: [ { name:'Charge credit card', type:'single', fields: [ { field:'amount', isRequired:true, description:'The amount (USD) to charge the credit card. Example: 42.50', type:'Number', }, { field:'description', isRequired:true, description:'Explain the reason why you want to charge manually the customer here', type:'String', }, ], }, ],});