Actions
Actions provide an opinionated and easy way to add business logic to a resource (model).
This guide walks you through the process of creating, managing, and testing actions in the RiseApp system, ensuring developers can onboard quickly and start contributing effectively.
Creating a New Action
To create a new action, you use the create_action management command. This command generates the necessary scaffolding for your action, including directories, files, and boilerplate code.
Command Syntax
web python manage.py create_action "Action Name" app.ModelNameExample
To create an action called extend_milestone for the Milestone model in the project app, run:
web python manage.py create_action "Extend milestone" project.MilestoneThis will:
- Create the necessary directories for the action.
- Generate the following files:
__init__.pytests/__init__.pyform.py(using thedrf/action_form.pytemplate)README.md(with documentation for the action)tests/test_behaviour.py(to test the action's behavior)tests/test_permissions.py(to test the action's permissions)
Files Created
Directory Structure
The created files will follow this structure:
app_name/
model_name/
actions/
action_name/
__init__.py
form.py
README.md
tests/
__init__.py
test_behaviour.py
test_permissions.pyPermissions
The create_action command will also automatically add the necessary permissions for the action. For example:
required_permissions = [get_action_permission_string(Milestone, ACTION_ID)]This will add the permission
milestone_can_perform_action_extend_milestonewhich will be created on startup. Restart the app if the permission is missing.
Adding Actions to API
Once an action is created, you need to make it accessible via the API. To do this, you use the ActionMixins class.
Steps to Add Actions
Import the
ActionMixins:pythonfrom drf.actionmixins import ActionMixinsMixin the
ActionMixinsinto yourViewSet:pythonfrom drf.actionmixins import ActionMixins class MyResourceViewSet(ActionMixins, viewsets.ModelViewSet): permission_classes = (ReadAndWriteDjangoModelPermissions,) queryset = MyModel.objects.all() filter_backends = [DjangoFilterBackend] filterset_class = MyModelFilterSetExpose Actions Automatically: The
ActionMixinswill automatically register the actions to the API, providing endpoints for both instance and bulk actions.
ActionMixins
The ActionMixins class simplifies exposing actions to your API. It includes built-in endpoints for listing and performing actions.
Key Methods
action_list: Lists all actions available for the model.perform_instance_action: Executes an instance-level action for a specific object.perform_bulk_action: Executes a bulk action for multiple objects.
Example Usage
class MilestoneViewSet(ActionMixins, viewsets.ModelViewSet):
permission_classes = (ReadAndWriteDjangoModelPermissions,)
queryset = Milestone.objects.all()
filter_backends = [DjangoFilterBackend]
filterset_class = MilestoneFilterSetThis exposes endpoints like:
GET /api/milestones/actions/- List all actions.POST /api/milestones/actions/<action_id>/- Perform a bulk action.POST /api/milestones/<pk>/actions/<action_id>/- Perform an instance action.
Special Actions
The create and delete actions are special cases that integrate with the standard REST API endpoints.
Create Action
- action at
{app_name}/{model_name}/actions/create/.. - will be called by the API's
createmethod. e.g.:POST /api/:resource/
Delete
- action at
{app_name}/{model_name}/actions/delete/.. - will be called by the API's
destroymethod. e.g.:DELETE /api/:resource/:id/
Permissions
Permissions for actions are custom-defined. Each action has its own permissions string generated during creation.
Example permission setup in the action form:
class ExtendMilestoneForm(forms.Form):
class Meta:
permissions = ["project.milestone_extend_action"]
permission_classes = [ExtendMilestonePermission]The permission_classes list defines custom logic for the action's permissions, while permissions defines required Django permissions.
Interacting with actions in the shell
Actions can be called directly from the Python shell for testing or debugging purposes. Here's how:
Instance Action:
from myapp.models import MyModel
instance = MyModel.objects.get(pk=1)
instance.perform_action(
actor=self.user,
action_id="action_name",
data={"field1": "value1", "field2": "value2"},
override_permissions=True,
)Bulk Action:
form, result = MyModel().perform_action(actor, action_id, data)Note: To use actions on a model, your model must mix in drf.actionmixins.ActionModel.
See example:
from drf.actionmixins import ActionModel
>
class MyModel(ActionModel, models.Model):
# ... model fields and methodsThis function takes a Django request object and the name of the action to perform. It will execute the action and return the result.
CLI (Command Line Interface)
The perform_action command is a CLI tool that allows you to execute actions on models.
# Execute an action on a model instance
python manage.py perform_action "project.Milestone" "extend_milestone" --actor-id 1 --pk=123
# Execute a bulk action on a model
python manage.py perform_action "project.Milestone" "extend_milestone" --actor-id 1
python manage.py perform_action --help # for more infoTesting
When an action is created, the command also generates test files to ensure proper behavior and permissions. The tests are located in the tests/ directory of the action.
Files
test_behaviour.py: Tests the core functionality of the action.test_permissions.py: Verifies that permissions are enforced correctly.
Running Tests
To run the tests for an action:
pytest app_name/model_name/actions/action_name/tests/Refer to the actions/tests folder for examples and best practices for writing tests.
Example Workflow
Here’s a complete example of creating and wiring up an action:
Create the action:
bashweb python manage.py create_action "Extend milestone" project.MilestoneImplement the logic in the generated
form.py.Add permissions in the
Metaclass of the form.Add the
ActionMixinsto theViewSetfor the model.Run tests to ensure the action works as expected:
bashpytest project/milestone/actions/extend_milestone/tests/Test the API endpoints manually or via automated API tests.
Activity Model Overview
The Activity model is designed to log actions and changes in the system. It includes:
actor: The user who performed the action.verb: A description of the action.targets: A list of objects affected by the action.data: A JSON field to log any additional data (e.g., changes in field values).
Querying Activities
Get a user’s activities:
pythonuser.activity_set.all()Get activities performed on an object:
pythonActivity.objects.for_object(instance)
By ensuring the __track_action method is included in all actions, you maintain an auditable log of all system changes, improving transparency and debugging capabilities.