
Security News
The Code You Didn't Write Is Still Yours to Defend
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.
django-approval-workflow
Advanced tools
A powerful, flexible Django package for implementing dynamic multi-step approval workflows
A powerful, flexible, and reusable Django package for implementing dynamic multi-step approval workflows in your Django applications.
advance_flow API that takes objects directlyextra_fields JSONField for custom data without package modificationspip install django-approval-workflow
Add approval_workflow to your INSTALLED_APPS:
INSTALLED_APPS = [
# ... your apps
'approval_workflow',
'mptt', # Required for hierarchical roles
'rest_framework', # Optional, for API endpoints
]
# Optional: Configure handlers like Django MIDDLEWARE
APPROVAL_HANDLERS = [
'myapp.handlers.DocumentApprovalHandler',
'myapp.handlers.TicketApprovalHandler',
'myapp.custom.StageApprovalHandler',
]
# Other optional settings
APPROVAL_ROLE_MODEL = "myapp.Role" # Default: None
APPROVAL_ROLE_FIELD = "role" # Default: "role"
APPROVAL_DYNAMIC_FORM_MODEL = "myapp.DynamicForm" # Default: None
APPROVAL_FORM_SCHEMA_FIELD = "form_info" # Default: "schema"
APPROVAL_HEAD_MANAGER_FIELD = "head_manager" # Default: None
python manage.py migrate approval_workflow
advance_flow InterfaceThe new interface eliminates the need to manually find approval instances:
from approval_workflow.services import start_flow, advance_flow
from django.contrib.auth import get_user_model
User = get_user_model()
# Create users
manager = User.objects.get(username='manager')
employee = User.objects.get(username='employee')
# Your model instance
document = MyDocument.objects.create(title="Important Document")
# Start an approval workflow
flow = start_flow(
obj=document,
steps=[
{"step": 1, "assigned_to": employee},
{"step": 2, "assigned_to": manager},
]
)
# โจ NEW: Simple interface - just pass the object and user
result = advance_flow(document, 'approved', employee, comment="Looks good!")
# โจ NEW: Automatic permission checking and error handling
try:
advance_flow(document, 'approved', unauthorized_user)
except PermissionError as e:
print(f"Access denied: {e}")
except ValueError as e:
print(f"No current approval found: {e}")
# Approve a document
advance_flow(document, 'approved', current_user, comment="Approved by manager")
# Reject with detailed feedback
advance_flow(ticket, 'rejected', current_user, comment="Missing required documentation")
# Request resubmission with additional review steps
advance_flow(
document,
'resubmission',
current_user,
comment="Need legal review before final approval",
resubmission_steps=[
{"step": 5, "assigned_to": legal_reviewer},
{"step": 6, "assigned_to": director}
]
)
# Delegate to another user
advance_flow(
ticket,
'delegated',
current_user,
comment="Delegating while on vacation",
delegate_to=specialist_user
)
# Escalate to higher authority
advance_flow(
document,
'escalated',
current_user,
comment="Escalating for executive approval"
)
Control the behavior and validation requirements for each approval step:
from approval_workflow.choices import ApprovalType
flow = start_flow(
obj=document,
steps=[
{
"step": 1,
"assigned_to": employee,
"approval_type": ApprovalType.SUBMIT, # Requires form with data
"form": submission_form
},
{
"step": 2,
"assigned_to": manager,
"approval_type": ApprovalType.APPROVE # Normal approval (default)
},
{
"step": 3,
"assigned_to": quality_checker,
"approval_type": ApprovalType.CHECK_IN_VERIFY # Verification step
},
{
"step": 4,
"assigned_to": admin,
"approval_type": ApprovalType.MOVE # Transfer without forms
}
]
)
Available Approval Types:
| Type | Form Behavior | Validation | Use Case |
|---|---|---|---|
APPROVE | Optional | If form exists with schema, validates only when form_data provided | Standard approval steps |
SUBMIT | Required | Form must be attached, form_data must be provided | Initial submission, data collection steps |
CHECK_IN_VERIFY | Optional | Two-phase: 1) Check-in, 2) Approval. Optional form validation | Quality checks, compliance verification |
MOVE | Rejected | Cannot have forms or form_data - raises error | Document routing, status changes |
Type-Specific Validation:
# SUBMIT type - enforces form requirement
advance_flow(
document,
'approved',
employee,
form_data={"field": "value"} # Required - will raise error if missing
)
# APPROVE type - optional form validation
advance_flow(document, 'approved', manager) # Works with or without form
advance_flow(document, 'approved', manager, form_data={...}) # Optional form_data
# MOVE type - rejects any forms
advance_flow(document, 'approved', admin) # No form allowed - pure transfer
# CHECK_IN_VERIFY type - two-phase verification flow
# Phase 1: Check-in
advance_flow(document, 'approved', quality_checker) # First call: checks in
# Phase 2: Normal approval
advance_flow(document, 'approved', quality_checker) # Second call: approves
CHECK_IN_VERIFY Two-Phase Flow:
# Step 1: User checks in (tracked in extra_fields)
result = advance_flow(expense, 'approved', auditor)
# Returns same instance - stays on current step
# extra_fields now contains: {"checked_in": True, "checked_in_by": "auditor", ...}
# Step 2: User approves after verification
result = advance_flow(expense, 'approved', auditor, comment="Verified - approved")
# Moves to next step - workflow progresses
Configure approval handlers in Django settings just like MIDDLEWARE:
# settings.py
APPROVAL_HANDLERS = [
'myapp.handlers.DocumentApprovalHandler',
'myapp.handlers.TicketApprovalHandler',
'myapp.custom.StageApprovalHandler',
]
Create powerful handlers with before/after hooks:
# myapp/handlers.py
from approval_workflow.handlers import BaseApprovalHandler
from django.core.mail import send_mail
class DocumentApprovalHandler(BaseApprovalHandler):
def before_approve(self, instance):
"""Called before approval processing starts."""
document = instance.flow.target
document.status = 'being_approved'
document.save()
def on_approve(self, instance):
"""Called during approval processing."""
print(f"Document {instance.flow.target.id} approved by {instance.action_user}")
def after_approve(self, instance):
"""Called after entire workflow completes successfully."""
document = instance.flow.target
document.status = 'published'
document.published_at = timezone.now()
document.save()
# Send publication notification
send_mail(
subject=f'Document "{document.title}" Published',
message='Your document has been approved and published.',
from_email='noreply@company.com',
recipient_list=[document.author.email],
)
def before_reject(self, instance):
"""Called before rejection processing."""
# Log rejection attempt
logger.info(f"Document {instance.flow.target.id} about to be rejected")
def after_reject(self, instance):
"""Called after workflow is rejected and terminated."""
document = instance.flow.target
document.status = 'rejected'
document.rejection_reason = instance.comment
document.save()
# Notify author of rejection
send_mail(
subject=f'Document "{document.title}" Rejected',
message=f'Your document was rejected: {instance.comment}',
from_email='noreply@company.com',
recipient_list=[document.author.email],
)
def on_resubmission(self, instance):
"""Called when resubmission is requested."""
document = instance.flow.target
document.status = 'needs_revision'
document.revision_requested_at = timezone.now()
document.save()
# Create revision task
RevisionTask.objects.create(
document=document,
requested_by=instance.action_user,
reason=instance.comment,
due_date=timezone.now() + timedelta(days=7)
)
Available Hook Methods:
before_approve(instance) - Called before approval startson_approve(instance) - Called during approval processingafter_approve(instance) - Called after workflow completes successfullybefore_reject(instance) - Called before rejection startson_reject(instance) - Called during rejection processingafter_reject(instance) - Called after workflow is rejected and terminatedbefore_resubmission(instance) - Called before resubmission startson_resubmission(instance) - Called during resubmission processingafter_resubmission(instance) - Called after resubmission workflow completesbefore_delegate(instance) - Called before delegation startson_delegate(instance) - Called during delegation processingafter_delegate(instance) - Called after delegated workflow completesbefore_escalate(instance) - Called before escalation startson_escalate(instance) - Called during escalation processingafter_escalate(instance) - Called after escalated workflow completeson_final_approve(instance) - Called when final step is approvedCreate sophisticated role-based approval workflows:
from approval_workflow.services import start_flow
from approval_workflow.choices import RoleSelectionStrategy
# Get role instances
manager_role = Role.objects.get(name="Manager")
director_role = Role.objects.get(name="Director")
# Create role-based workflow with different strategies
flow = start_flow(
obj=document,
steps=[
{
"step": 1,
"assigned_role": manager_role,
"role_selection_strategy": RoleSelectionStrategy.ANYONE,
# Any manager can approve this step
},
{
"step": 2,
"assigned_role": director_role,
"role_selection_strategy": RoleSelectionStrategy.CONSENSUS,
# All directors must approve this step
}
]
)
# Mixed role-based and user-based workflow
mixed_flow = start_flow(
obj=document,
steps=[
{"step": 1, "assigned_to": specific_user}, # User-based step
{
"step": 2,
"assigned_role": manager_role,
"role_selection_strategy": RoleSelectionStrategy.ROUND_ROBIN,
# Automatically assigns to manager with least workload
}
]
)
# The new advance_flow automatically handles role-based permissions
advance_flow(document, 'approved', manager_with_role) # โ
Works if user has the role
advance_flow(document, 'approved', user_without_role) # โ Raises PermissionError
Role Selection Strategies (Supported):
ANYONE: Any user with the role can approve (first approval completes the step)CONSENSUS: All users with the role must approve before advancingROUND_ROBIN: Automatically assigns to the user with the least current assignmentsQUORUM: Require N approvals out of M usersMAJORITY: Require >50% approvalsPERCENTAGE: Require a specific percentage (inclusive) of approvalsHIERARCHY_UP: Approvals from N levels up in a role hierarchyHIERARCHY_CHAIN: Approvals from the base user plus N levels upNote: Additional strategy labels like MANAGEMENT_PATH, DYNAMIC_*, LEAD_ONLY, SENIORITY_BASED, and WORKLOAD_BALANCED are reserved for future support and are rejected at validation time.
The package includes advanced approval strategies for enterprise use cases:
Require a specific number of approvals from a group:
from approval_workflow.choices import RoleSelectionStrategy
from datetime import datetime, timedelta
# Example: 2 out of 5 committee members must approve
flow = start_flow(
obj=expense_request,
steps=[
{
"step": 1,
"assigned_role": committee_role,
"role_selection_strategy": RoleSelectionStrategy.QUORUM,
"quorum_count": 2, # Need 2 approvals
"quorum_total": 5, # Out of 5 total users
"due_date": datetime.now() + timedelta(days=3),
"escalation_on_timeout": True,
"timeout_action": "escalate",
}
]
)
# First committee member approves
advance_flow(expense_request, 'approved', committee_member1)
# Status: Still CURRENT (1/2 approvals)
# Second committee member approves
advance_flow(expense_request, 'approved', committee_member2)
# Status: APPROVED! (2/2 reached - remaining instances auto-cancelled)
Real-World Use Case: Purchase Request Approval
# Purchase requests over $10,000 need 2 out of 5 finance committee approvals
def create_purchase_request_workflow(purchase_request):
if purchase_request.amount > 10000:
return start_flow(
obj=purchase_request,
steps=[
{
"step": 1,
"assigned_role": finance_committee_role,
"role_selection_strategy": RoleSelectionStrategy.QUORUM,
"quorum_count": 2,
"quorum_total": 5,
"due_date": datetime.now() + timedelta(days=5),
"timeout_action": "escalate",
"escalation_on_timeout": True,
}
]
)
Require majority (>50%) of role users to approve:
# If role has 5 users, need 3 approvals (majority of 5)
flow = start_flow(
obj=contract,
steps=[
{
"step": 1,
"assigned_role": board_members_role,
"role_selection_strategy": RoleSelectionStrategy.MAJORITY,
# Automatically calculates >50% requirement
"due_date": datetime.now() + timedelta(days=7),
}
]
)
Require specific percentage of approvals:
# Need 2/3 (66.67%) of users to approve
flow = start_flow(
obj=strategic_plan,
steps=[
{
"step": 1,
"assigned_role": stakeholders_role,
"role_selection_strategy": RoleSelectionStrategy.PERCENTAGE,
"percentage_required": 66.67, # 2/3 majority
"due_date": datetime.now() + timedelta(days=14),
}
]
)
An example workflow for budget approvals with thresholds:
from approval_workflow.services import start_flow
from approval_workflow.choices import RoleSelectionStrategy, ApprovalType
def create_budget_control_flow(budget_request):
steps = [
# Step 1: Requester submits with a form
{
"step": 1,
"assigned_to": budget_request.requester,
"approval_type": ApprovalType.SUBMIT,
"form": budget_request.form,
},
]
if budget_request.amount <= 5000:
# Team lead approval
steps.append(
{
"step": 2,
"assigned_role": budget_request.team_lead_role,
"role_selection_strategy": RoleSelectionStrategy.ANYONE,
}
)
elif budget_request.amount <= 20000:
# Finance committee quorum
steps.append(
{
"step": 2,
"assigned_role": budget_request.finance_committee_role,
"role_selection_strategy": RoleSelectionStrategy.QUORUM,
"quorum_count": 2,
"quorum_total": 5,
}
)
else:
# Executive chain approval
steps.append(
{
"step": 2,
"assigned_role": budget_request.requester_role,
"role_selection_strategy": RoleSelectionStrategy.HIERARCHY_UP,
"hierarchy_levels": 2,
"hierarchy_base_user": budget_request.requester,
}
)
steps.append(
{
"step": 3,
"assigned_role": budget_request.cfo_role,
"role_selection_strategy": RoleSelectionStrategy.ANYONE,
}
)
return start_flow(obj=budget_request, steps=steps)
Automatically escalate through organizational hierarchy levels:
# Example: Deal approval - Account Manager โ Manager โ Director โ VP
# The number of levels depends on deal amount
def create_deal_approval_workflow(deal):
# Determine hierarchy levels based on deal amount
if deal.amount < 50000:
levels = 1 # Account Manager โ Manager only
elif deal.amount < 100000:
levels = 2 # Account Manager โ Manager โ Director
else:
levels = 3 # Account Manager โ Manager โ Director โ VP
return start_flow(
obj=deal,
steps=[
{
"step": 1,
"assigned_role": account_manager_role,
"role_selection_strategy": RoleSelectionStrategy.HIERARCHY_UP,
"hierarchy_levels": levels,
"hierarchy_base_user": deal.account_manager, # Start from this user's role
"due_date": datetime.now() + timedelta(days=5),
}
]
)
# Usage:
deal = Deal.objects.create(
account_manager=account_manager_user,
amount=75000,
client="Acme Corp"
)
# This creates approval instances for:
# 1. Manager (1 level up from account_manager)
# 2. Director (2 levels up from account_manager)
flow = create_deal_approval_workflow(deal)
# Account manager approves (not in approval chain, automatically skipped)
# Manager approves
advance_flow(deal, 'approved', manager_user)
# Status: Still CURRENT (waiting for director)
# Director approves
advance_flow(deal, 'approved', director_user)
# Status: APPROVED! All levels completed
How HIERARCHY_UP Works:
hierarchy_base_user (e.g., deal.account_manager)user.role)parent attributeRole Hierarchy Setup (using MPTT):
# Create role hierarchy
vp_role = Role.objects.create(name="VP", parent=None)
director_role = Role.objects.create(name="Director", parent=vp_role)
manager_role = Role.objects.create(name="Manager", parent=director_role)
account_manager_role = Role.objects.create(name="Account Manager", parent=manager_role)
# Assign users to roles
account_manager = User.objects.create(username="john_doe", role=account_manager_role)
manager = User.objects.create(username="jane_smith", role=manager_role)
director = User.objects.create(username="bob_johnson", role=director_role)
vp = User.objects.create(username="alice_williams", role=vp_role)
# When deal with hierarchy_levels=2 is created:
# - Manager approves (1 level up)
# - Director approves (2 levels up)
# VP is NOT included (only 2 levels requested)
Require approval from entire chain (base user + all levels up):
flow = start_flow(
obj=purchase_order,
steps=[
{
"step": 1,
"assigned_role": employee_role,
"role_selection_strategy": RoleSelectionStrategy.HIERARCHY_CHAIN,
"hierarchy_levels": 3, # Employee + Manager + Director + VP
"hierarchy_base_user": purchase_order.requester,
}
]
)
# Creates approvals for:
# - Employee (base user)
# - Manager (1 level up)
# - Director (2 levels up)
# - VP (3 levels up)
# ALL must approve before advancing
Configure deadlines and automatic actions:
from datetime import datetime, timedelta
flow = start_flow(
obj=time_sensitive_request,
steps=[
{
"step": 1,
"assigned_to": manager,
"due_date": datetime.now() + timedelta(days=2),
"reminder_sent": False,
"escalation_on_timeout": True,
"timeout_action": "escalate", # or "delegate", "auto_approve", "reject"
}
]
)
# Check for timeout in your management command or cron job
from approval_workflow.models import ApprovalInstance
def check_timeouts():
"""Check for overdue approvals and take action."""
overdue = ApprovalInstance.objects.filter(
status=ApprovalStatus.CURRENT,
due_date__lt=timezone.now()
)
for instance in overdue:
if instance.escalation_on_timeout:
if instance.timeout_action == "escalate":
# Escalate to higher authority
advance_flow(
instance.flow.target,
'escalated',
instance.assigned_to,
comment="Auto-escalated due to timeout"
)
elif instance.timeout_action == "reject":
# Auto-reject
advance_flow(
instance.flow.target,
'rejected',
instance.assigned_to,
comment="Auto-rejected due to timeout"
)
Track delegation and escalation history:
# Delegate approval
advance_flow(
document,
'delegated',
manager,
delegate_to=acting_manager,
comment="Delegating while on vacation"
)
# The delegation_chain is automatically tracked:
# [
# {
# "from_user": "manager",
# "to_user": "acting_manager",
# "timestamp": "2024-01-15T10:30:00Z",
# "reason": "Delegating while on vacation"
# }
# ]
# Escalate approval
advance_flow(
document,
'escalated',
manager,
comment="Escalating to director - requires executive review"
)
# Escalation level is tracked:
# escalation_level: 0 โ 1 โ 2
Run multiple approval tracks simultaneously:
# Parallel approval tracks for different aspects
flow = start_flow(
obj=project_proposal,
steps=[
# Track 1: Technical approval (parallel_group="technical")
{
"step": 1,
"assigned_role": tech_lead_role,
"role_selection_strategy": RoleSelectionStrategy.ANYONE,
"parallel_group": "technical",
"parallel_required": True, # Must complete before step 2
},
# Track 2: Business approval (parallel_group="business")
{
"step": 1,
"assigned_role": product_manager_role,
"role_selection_strategy": RoleSelectionStrategy.ANYONE,
"parallel_group": "business",
"parallel_required": True, # Must complete before step 2
},
# Step 2: Only starts after BOTH parallel tracks complete
{
"step": 2,
"assigned_to": director,
}
]
)
# Both technical and business tracks can approve in parallel
# Step 2 only becomes CURRENT after BOTH are approved
All approval choices are translatable:
from django.utils.translation import gettext_lazy as _
# In your templates or views
from approval_workflow.choices import RoleSelectionStrategy
# Get translated label
strategy_label = RoleSelectionStrategy.QUORUM.label
# Returns: "Require N out of M users to approve (configurable)"
# (or translated version if active language is not English)
# Use in admin or forms
class ApprovalStepForm(forms.Form):
strategy = forms.ChoiceField(
choices=[(s.value, s.label) for s in RoleSelectionStrategy]
)
All workflow events are logged with structured, emoji-indicated messages:
# Log format:
# [APPROVAL_WORKFLOW] โจ NEW INSTANCE CREATED | Flow ID: 123 | Step: 1 | Status: CURRENT | ...
# [APPROVAL_WORKFLOW] โ
APPROVED | Flow ID: 123 | Step: 1 | Action User: john_doe | ...
# [APPROVAL_WORKFLOW] โ REJECTED | Flow ID: 123 | Step: 2 | Action User: jane_smith | ...
# [APPROVAL_WORKFLOW] ๐ DELEGATED | Flow ID: 123 | From: manager | To: acting_manager | ...
# [APPROVAL_WORKFLOW] โฌ๏ธ ESCALATED | Flow ID: 123 | Level: 1 โ 2 | ...
# Configure logging in settings.py
LOGGING = {
'version': 1,
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': 'approvals.log',
},
},
'loggers': {
'approval_workflow': {
'handlers': ['file'],
'level': 'INFO',
},
},
}
# Create form with validation schema
expense_form = DynamicForm.objects.create(
name="Expense Approval Form",
schema={
"type": "object",
"properties": {
"amount": {"type": "number", "minimum": 0},
"category": {"type": "string", "enum": ["travel", "equipment"]},
"description": {"type": "string", "minLength": 10}
},
"required": ["amount", "category", "description"]
}
)
# Use in workflow with automatic validation
flow = start_flow(
obj=expense_request,
steps=[
{"step": 1, "assigned_to": manager, "form": expense_form}
]
)
# Form data is validated automatically
advance_flow(
expense_request,
'approved',
manager,
form_data={
"amount": 750.00,
"category": "travel",
"description": "Conference attendance in NYC"
}
)
# Add custom metadata to approval steps
flow = start_flow(
obj=document,
steps=[
{
"step": 1,
"assigned_to": manager,
"extra_fields": {
"priority": "high",
"department": "IT",
"requires_signature": True,
"custom_deadline": "2024-12-31",
"tags": ["urgent", "compliance"]
}
}
]
)
# Access in handlers
class CustomApprovalHandler(BaseApprovalHandler):
def on_approve(self, instance):
priority = instance.extra_fields.get("priority", "normal")
if priority == "high":
send_urgent_notification(instance.assigned_to)
from approval_workflow.utils import get_approval_repository, get_approval_summary
# Single optimized query for all operations
repo = get_approval_repository(document)
current = repo.get_current_approval() # O(1) lookup
next_step = repo.get_next_approval() # No additional database hit
progress = repo.get_workflow_progress() # Efficient progress calculation
# Or get comprehensive summary
summary = get_approval_summary(document)
print(f"Progress: {summary['progress_percentage']}%")
print(f"Current step: {summary['current_step'].step_number}")
# Extend existing workflows dynamically
new_instances = extend_flow(
flow=flow,
steps=[
{"step": 3, "assigned_to": legal_reviewer},
{"step": 4, "assigned_role": director_role, "role_selection_strategy": RoleSelectionStrategy.CONSENSUS}
]
)
The package maintains full backward compatibility:
# OLD interface (still works)
current_step = get_current_approval(document)
advance_flow(instance=current_step, action='approved', user=user)
# NEW interface (recommended)
advance_flow(document, 'approved', user)
Run the comprehensive test suite:
# Install development dependencies
pip install -r requirements-dev.txt
# Run all tests (128 tests)
pytest
# Run with coverage
pytest --cov=approval_workflow
INSTALLED_APPS = [
'approval_workflow',
'mptt', # For hierarchical roles
]
# Handler configuration (MIDDLEWARE style)
APPROVAL_HANDLERS = [
'myapp.handlers.DocumentApprovalHandler',
'myapp.handlers.TicketApprovalHandler',
]
# Role model configuration
APPROVAL_ROLE_MODEL = "myapp.Role" # Must inherit from MPTTModel
APPROVAL_ROLE_FIELD = "role" # Field linking User to Role
# Form integration
APPROVAL_DYNAMIC_FORM_MODEL = "myapp.DynamicForm"
APPROVAL_FORM_SCHEMA_FIELD = "schema" # Field containing JSON schema
# Escalation configuration
APPROVAL_HEAD_MANAGER_FIELD = "head_manager" # Direct manager field
# Language/Locale configuration
LANGUAGE_CODE = 'en-us' # Default language
USE_I18N = True
USE_L10N = True
LOCALE_PATHS = [
'/path/to/your/project/locale',
'/path/to/approval_workflow/locale', # Include package translations
]
LANGUAGES = [
('en', 'English'),
('ar', 'Arabic'),
# Add more languages as needed
]
The package includes full internationalization support with Arabic translations included out of the box:
Available Languages:
Using Translations in Your Project:
# settings.py
LANGUAGE_CODE = 'ar' # Set Arabic as default
USE_I18N = True
LOCALE_PATHS = [
BASE_DIR / 'locale',
BASE_DIR / 'approval_workflow' / 'locale', # Include package translations
]
MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware', # Add this
# ... other middleware
]
from django.utils.translation import activate, get_language
# Set language to Arabic
activate('ar')
# Or let Django detect from request
# (requires LocaleMiddleware in MIDDLEWARE)
{% load i18n %}
<!-- Get translated label -->
{% get_current_language as LANGUAGE_CODE %}
<h1>{% trans "Approval Flow" %}</h1>
<!-- Switch language -->
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="language" type="hidden" value="ar">
<input type="submit" value="ุงูุนุฑุจูุฉ">
</form>
from approval_workflow.choices import RoleSelectionStrategy
# Get translated label based on active language
strategy_label = RoleSelectionStrategy.QUORUM.label
# English: "Require N out of M users to approve (configurable)"
# Arabic: "ูุชุทูุจ ู
ูุงููุฉ N ู
ู ุฃุตู M ู
ุณุชุฎุฏู
ูู (ูุงุจู ููุชูููู)"
Adding More Languages:
To add support for more languages:
mkdir -p approval_workflow/locale/<lang_code>/LC_MESSAGES
Copy and translate the django.po file
Compile translations:
msgfmt -o approval_workflow/locale/<lang_code>/LC_MESSAGES/django.mo \
approval_workflow/locale/<lang_code>/LC_MESSAGES/django.po
For Arabic users, the package is ready to use out of the box:
# In your Django settings
LANGUAGE_CODE = 'ar'
# All approval choices, messages, and labels will automatically display in Arabic
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
Mohamed Salah
Email: info@codxi.com
GitHub: Codxi-Co
Key Improvements in Latest Version:
advance_flow(object, action, user) APIFor detailed examples and advanced usage, see the ENHANCED_FEATURES.md documentation and test files.
FAQs
A powerful, flexible Django package for implementing dynamic multi-step approval workflows
The pypi package django-approval-workflow receives a total of 108 weekly downloads. As such, django-approval-workflow popularity was classified as not popular.
We found that django-approval-workflow demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.