Viewflow uses a structured approach for template lookup, allowing developers to customize the presentation layer of different workflow views. This approach supports a hierarchy of template customizations, ranging from global overrides to task-specific adjustments. Below, we detail how to customize templates for three primary types of views: Detail View, Start Process View, and Update Process View.
When rendering a view, Viewflow searches for templates in the following order:
The placeholders used are:
The Detail View presents detailed information about a specific task within a flow. To customize this view:
The Create Process View is used to initiate a new process. To customize this view, use start.html as self.template_filename. Following the lookup order, you can customize:
The Update Process View is employed when updating an ongoing process. The customization approach is similar, utilizing task.html as self.template_filename:
The process_data.html template is a shared resource within Viewflow, utilized by multiple views to present process-related data. Customizing this template allows for a unified appearance and behavior across different parts of your workflow application. Below are the steps to override the process_data.html template at both the flow-specific and global levels.
Viewflow searches for the process_data.html template using the following order:
Understanding the context variables available in different templates is crucial for customization:
Context variables available in task_detail.html:
{
'activation': TaskActivation instance,
'task': Task model instance,
'flow_class': The Flow class,
'flow_task': The specific Node instance,
'form': Form instance (if applicable)
}
Context variables available in process_detail.html:
{
'process': Process model instance,
'flow_class': The Flow class,
'tasks': QuerySet of Task instances related to the process
}
Context variables available in task_list.html:
{
'flow_class': The Flow class (optional, may be None for inbox/queue views),
'task_list': QuerySet of Task instances,
'filter_form': Task filter form (if filtering is enabled)
}
Context variables available in process_list.html:
{
'flow_class': The Flow class,
'process_list': QuerySet of Process instances,
'filter_form': Process filter form (if filtering is enabled)
}
Here’s an example of a customized task detail template that enhances the default view with additional information:
{# myapp/myapp/approve_task_detail.html #}
{% extends 'viewflow/workflow/task_detail.html' %}
{% block task_details %}
<div class="custom-task-details">
<div class="task-header">
<h3>{{ task.flow_task.task_title }}</h3>
<span class="task-status status-{{ task.status }}">{{ task.get_status_display }}</span>
</div>
<div class="task-meta">
<p><strong>Created:</strong> {{ task.created|date:"F j, Y, H:i" }}</p>
{% if task.owner %}
<p><strong>Assigned to:</strong> {{ task.owner.get_full_name|default:task.owner.username }}</p>
{% endif %}
{% if task.started %}
<p><strong>Started:</strong> {{ task.started|date:"F j, Y, H:i" }}</p>
{% endif %}
</div>
<div class="task-actions">
{% if task|can_execute:request.user %}
<a href="{% url 'myapp:execute_task' process_pk=task.process_id task_pk=task.pk %}" class="btn btn-primary">
Process Task
</a>
{% endif %}
{% if task|can_assign:request.user and not task.owner %}
<a href="{% url 'myapp:assign_task' process_pk=task.process_id task_pk=task.pk %}" class="btn btn-outline">
Assign to Me
</a>
{% endif %}
</div>
</div>
{% endblock %}
Here’s an example of how to customize the process_data.html template to display business-specific information:
{# myapp/myapp/process_data.html #}
<div class="process-data-container">
<h3>Process Information</h3>
<div class="process-field">
<label>Process ID:</label>
<span>{{ process.pk }}</span>
</div>
<div class="process-field">
<label>Started:</label>
<span>{{ process.created|date:"F j, Y, H:i" }}</span>
</div>
<div class="process-field">
<label>Status:</label>
<span class="status-badge status-{{ process.status }}">{{ process.get_status_display }}</span>
</div>
{% if process.finished %}
<div class="process-field">
<label>Completed:</label>
<span>{{ process.finished|date:"F j, Y, H:i" }}</span>
</div>
{% endif %}
{% if process.artifact %}
<div class="process-field">
<label>Artifact:</label>
<div class="artifact-details">
<span>{{ process.artifact }}</span>
<a href="{{ process.artifact.get_absolute_url }}" class="view-artifact-link">View Details</a>
</div>
</div>
{% endif %}
{# Custom fields from your process model #}
{% if process.approved is not None %}
<div class="process-field">
<label>Approval Status:</label>
<span class="approval-status {% if process.approved %}approved{% else %}rejected{% endif %}">
{{ process.approved|yesno:"Approved,Rejected" }}
</span>
</div>
{% endif %}
{% if process.comments %}
<div class="process-field wide">
<label>Comments:</label>
<div class="process-comments">{{ process.comments|linebreaks }}</div>
</div>
{% endif %}
</div>
Viewflow provides several template tags and filters to help with workflow template customization:
{% load viewflow workflow %}
{# Generate URLs for task actions #}
<a href="{% task_action_url task 'assign' %}">Assign</a>
<a href="{% task_action_url task 'execute' %}">Execute</a>
<a href="{% task_action_url task 'unassign' %}">Unassign</a>
{# Generate URLs for process actions #}
<a href="{% process_action_url process 'cancel' %}">Cancel Process</a>
<a href="{% process_action_url process 'detail' %}">View Details</a>
{# Render flow diagram with current task highlighted #}
{% flow_diagram flow_class task=task %}
{# Check if user can perform task actions #}
{% if task|can_execute:request.user %}
<button type="button">Execute Task</button>
{% endif %}
{% if task|can_assign:request.user %}
<button type="button">Assign Task</button>
{% endif %}
{# Format task status with appropriate styling #}
<span class="status-badge" style="background-color: {{ task.status|task_status_color }}">
{{ task.get_status_display }}
</span>
Add custom CSS styles to enhance the appearance of your workflow interfaces:
/* Task status indicators */
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
font-weight: bold;
}
.status-NEW { background-color: #e3f2fd; color: #0d47a1; }
.status-ASSIGNED { background-color: #fff8e1; color: #ff6f00; }
.status-STARTED { background-color: #e8f5e9; color: #2e7d32; }
.status-DONE { background-color: #e0f2f1; color: #00695c; }
.status-CANCELED { background-color: #ffebee; color: #b71c1c; }
.status-ERROR { background-color: #ffebee; color: #b71c1c; }
/* Process data styling */
.process-data-container {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
background-color: #fafafa;
margin-bottom: 24px;
}
.process-field {
display: flex;
margin-bottom: 8px;
border-bottom: 1px dotted #e0e0e0;
padding-bottom: 8px;
}
.process-field label {
flex: 0 0 150px;
font-weight: bold;
color: #424242;
}
.process-field span {
flex: 1;
}
.process-field.wide {
flex-direction: column;
}
.process-field.wide label {
margin-bottom: 8px;
}
/* Task list styling */
.task-list-container .task-item {
display: flex;
align-items: center;
padding: 12px;
border-bottom: 1px solid #e0e0e0;
transition: background-color 0.2s;
}
.task-list-container .task-item:hover {
background-color: #f5f5f5;
}
.task-list-container .task-item .task-title {
flex: 1;
font-weight: 500;
}
.task-list-container .task-item .task-meta {
flex: 0 0 200px;
color: #757575;
font-size: 0.9em;
}
.task-list-container .task-item .task-status {
flex: 0 0 120px;
text-align: right;
}
When customizing Viewflow templates, consider these best practices: