Skip to content

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

bash
web python manage.py create_action "Action Name" app.ModelName

Example

To create an action called extend_milestone for the Milestone model in the project app, run:

bash
web python manage.py create_action "Extend milestone" project.Milestone

This will:

  1. Create the necessary directories for the action.
  2. Generate the following files:
    • __init__.py
    • tests/__init__.py
    • form.py (using the drf/action_form.py template)
    • 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.py

Permissions

The create_action command will also automatically add the necessary permissions for the action. For example:

python
required_permissions = [get_action_permission_string(Milestone, ACTION_ID)]

This will add the permission milestone_can_perform_action_extend_milestone which 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

  1. Import the ActionMixins:

    python
    from drf.actionmixins import ActionMixins
  2. Mixin the ActionMixins into your ViewSet:

    python
    from drf.actionmixins import ActionMixins
    
    class MyResourceViewSet(ActionMixins, viewsets.ModelViewSet):
        permission_classes = (ReadAndWriteDjangoModelPermissions,)
        queryset = MyModel.objects.all()
        filter_backends = [DjangoFilterBackend]
        filterset_class = MyModelFilterSet
  3. Expose Actions Automatically: The ActionMixins will 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

  1. action_list: Lists all actions available for the model.

  2. perform_instance_action: Executes an instance-level action for a specific object.

  3. perform_bulk_action: Executes a bulk action for multiple objects.

Example Usage

python
class MilestoneViewSet(ActionMixins, viewsets.ModelViewSet):
    permission_classes = (ReadAndWriteDjangoModelPermissions,)
    queryset = Milestone.objects.all()
    filter_backends = [DjangoFilterBackend]
    filterset_class = MilestoneFilterSet

This 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 create method. e.g.: POST /api/:resource/

Delete

  • action at {app_name}/{model_name}/actions/delete/..
  • will be called by the API's destroy method. 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:

python
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:

python
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:

python

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:

python
from drf.actionmixins import ActionModel
>
class MyModel(ActionModel, models.Model):
    # ... model fields and methods

This 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.

bash
# 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 info

Testing

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

  1. test_behaviour.py: Tests the core functionality of the action.

  2. test_permissions.py: Verifies that permissions are enforced correctly.

Running Tests

To run the tests for an action:

bash
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:

  1. Create the action:

    bash
    web python manage.py create_action "Extend milestone" project.Milestone
  2. Implement the logic in the generated form.py.

  3. Add permissions in the Meta class of the form.

  4. Add the ActionMixins to the ViewSet for the model.

  5. Run tests to ensure the action works as expected:

    bash
    pytest project/milestone/actions/extend_milestone/tests/
  6. 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:

    python
    user.activity_set.all()
  • Get activities performed on an object:

    python
    Activity.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.