django-approval-workflow

news/2025/11/15 23:51:36/文章来源:https://www.cnblogs.com/lightsong/p/19226552

django-approval-workflow

https://github.com/Codxi-Co/django-approval-workflow

A powerful, flexible, and reusable Django package for implementing dynamic multi-step approval workflows in your Django applications.

✨ Features

  • 🚀 Simplified Interface: New developer-friendly advance_flow API that takes objects directly
  • ⚙️ MIDDLEWARE-Style Configuration: Configure handlers in settings just like Django MIDDLEWARE
  • 🔄 Dynamic Workflow Creation: Create approval workflows for any Django model using GenericForeignKey
  • 👥 Multi-Step Approval Process: Support for sequential approval steps with role-based assignments
  • 🎯 Approval Types: Four specialized types (APPROVE, SUBMIT, CHECK_IN_VERIFY, MOVE) with type-specific validation
  • 🎭 Role-Based Approvals: Three strategies (ANYONE, CONSENSUS, ROUND_ROBIN) for dynamic role-based approvals
  • 🔐 Automatic Permission Validation: Built-in user authorization for both direct and role-based assignments
  • 🔗 Role-Based Permissions: Hierarchical role support using MPTT (Modified Preorder Tree Traversal)
  • ⚡ High-Performance Architecture: Enterprise-level optimizations with O(1) lookups and intelligent caching
  • 📊 Repository Pattern: Centralized data access with single-query optimizations
  • 🔄 Flexible Actions: Approve, reject, delegate, escalate, or request resubmission at any step
  • 🎯 Enhanced Hook System: Before and after hooks for complete workflow lifecycle control
  • 🧩 Custom Fields Support: Extensible extra_fields JSONField for custom data without package modifications
  • ⏰ SLA Tracking: Built-in SLA duration tracking for approval steps
  • 🌐 REST API Ready: Built-in REST API endpoints using Django REST Framework
  • 🛠️ Django Admin Integration: Full admin interface for managing workflows
  • 🎨 Extensible Handlers: Custom hook system for workflow events with settings-based configuration
  • 📝 Form Integration: Optional dynamic form support for approval steps
  • ✅ Comprehensive Testing: Full test suite with pytest (81+ tests passing)
  • 🔄 Backward Compatibility: Maintains compatibility with existing implementations

 

https://github.com/Codxi-Co/django-approval-workflow/blob/develop/approval_workflow/models.py

"""Models for approval_workflow Django app.Includes core models to support dynamic multi-step approval flows
attached to arbitrary Django models using GenericForeignKey.Author: Mohamed Salah
"""import loggingfrom django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import modelsfrom .choices import ApprovalStatus, ApprovalType, RoleSelectionStrategylogger = logging.getLogger(__name__)
User = get_user_model()class ApprovalFlow(models.Model):"""Represents a reusable approval flow attached to a specific object.This model uses GenericForeignKey to dynamically associate a flowto any model instance (e.g., Ticket, Stage, etc.)."""content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)object_id = models.CharField(max_length=255)target = GenericForeignKey("content_type", "object_id")created_at = models.DateTimeField(auto_now_add=True)class Meta:"""Meta options for ApprovalFlow model."""indexes = [# Composite index for efficient flow lookups by object
            models.Index(fields=["content_type", "object_id"], name="approval_flow_object_idx"),]# Ensure one flow per objectunique_together = ["content_type", "object_id"]def __str__(self):return f"Flow for {self.content_type.app_label}.{self.content_type.model}({self.object_id})"def save(self, *args, **kwargs):"""Override save to add logging."""is_new = self._state.addingsuper().save(*args, **kwargs)if is_new:logger.info("New approval flow created - Flow ID: %s, Object: %s.%s (%s)",self.pk,self.content_type.app_label,self.content_type.model,self.object_id,)else:logger.debug("Approval flow updated - Flow ID: %s", self.pk)class ApprovalInstance(models.Model):"""Tracks the progress of an approval flow.Merges the concept of "step" into this model directly, where eachinstance represents the current step in the flow and can be updatedwith approval/rejection logic.The instance also stores the role responsible for the step."""flow = models.ForeignKey(ApprovalFlow, on_delete=models.CASCADE, related_name="instances")form_data = models.JSONField(null=True, blank=True)# Dynamic form using GenericForeignKey to avoid migrationsform_content_type = models.ForeignKey(ContentType,on_delete=models.SET_NULL,null=True,blank=True,related_name="approval_forms",help_text="Content type of the dynamic form model",)form_object_id = models.CharField(max_length=255, null=True, blank=True)form = GenericForeignKey("form_content_type", "form_object_id")step_number = models.PositiveIntegerField(default=1, help_text="The current step in the flow")assigned_to = models.ForeignKey(User,on_delete=models.SET_NULL,null=True,blank=True,help_text="User currently assigned to act on this step",)action_user = models.ForeignKey(User,on_delete=models.SET_NULL,null=True,blank=True,related_name="approval_actions",help_text="User who actually performed the approve/reject action",)status = models.CharField(max_length=30,choices=ApprovalStatus,default=ApprovalStatus.PENDING,help_text="Current approval status",)approval_type = models.CharField(max_length=20,choices=ApprovalType,default=ApprovalType.APPROVE,help_text="Type of approval action (approve, submit, check-in/verify, move)",)comment = models.TextField(blank=True)# SLA trackingsla_duration = models.DurationField(null=True,blank=True,help_text="SLA duration for this step (e.g., 2 days, 4 hours). Optional.",)# Role hierarchy permissionsallow_higher_level = models.BooleanField(default=False,help_text="Allow users with higher roles to approve this step on behalf of assigned user",)# Role-based approval fieldsassigned_role_content_type = models.ForeignKey(ContentType,on_delete=models.SET_NULL,null=True,blank=True,related_name="approval_roles",help_text="Content type of the role model from settings",)assigned_role_object_id = models.CharField(max_length=255, null=True, blank=True, help_text="ID of the role instance")assigned_role = GenericForeignKey("assigned_role_content_type", "assigned_role_object_id")role_selection_strategy = models.CharField(max_length=20,choices=RoleSelectionStrategy,null=True,blank=True,help_text="Strategy for selecting approvers when assigned to a role",)# Additional fields for custom dataextra_fields = models.JSONField(null=True,blank=True,help_text="Additional custom fields for extending functionality without package modifications",)started_at = models.DateTimeField(auto_now_add=True)updated_at = models.DateTimeField(auto_now=True)class Meta:"""Meta options for ApprovalInstance model."""ordering = ["-started_at"]indexes = [# OPTIMIZED: Single strategic index for CURRENT status O(1) lookupsmodels.Index(fields=["flow", "status"], name="appinst_flow_status_idx"),# Index for finding approvals by assigned user (dashboard queries)
            models.Index(fields=["assigned_to", "status"], name="appinst_assigned_status_idx"),# Index for temporal queries (reporting/analytics)models.Index(fields=["started_at"], name="appinst_started_at_idx"),]constraints = [# For user-assigned approvals, ensure only one CURRENT status per flow# For role-based approvals, we allow multiple CURRENT instances
            models.UniqueConstraint(fields=["flow"],condition=models.Q(status="current")& models.Q(assigned_to__isnull=False)& models.Q(assigned_role_content_type__isnull=True),name="unique_current_per_flow_user",),]def __str__(self):return f"{self.flow} - Step {self.step_number} [{self.status}]"def __repr__(self):return f"<ApprovalInstance flow_id={self.flow.id} step={self.step_number} status={self.status}>"def save(self, *args, **kwargs):"""Override save to add logging and auto-set form_content_type."""is_new = self._state.addingold_status = None# Auto-set form_content_type from settings if not already setif not self.form_content_type:form_model_path = getattr(settings, "APPROVAL_DYNAMIC_FORM_MODEL", None)if form_model_path:try:app_label, model_name = form_model_path.split(".", 1)content_type = ContentType.objects.get(app_label=app_label, model=model_name.lower())self.form_content_type = content_typeexcept (ValueError, ContentType.DoesNotExist) as e:logger.warning("Invalid APPROVAL_DYNAMIC_FORM_MODEL setting: %s - %s",form_model_path,e,)# Auto-set role_content_type from settings if not already setif not self.assigned_role_content_type and self.assigned_role_object_id:role_model_path = getattr(settings, "APPROVAL_ROLE_MODEL", None)if role_model_path:try:app_label, model_name = role_model_path.split(".", 1)content_type = ContentType.objects.get(app_label=app_label, model=model_name.lower())self.assigned_role_content_type = content_typeexcept (ValueError, ContentType.DoesNotExist) as e:logger.warning("Invalid APPROVAL_ROLE_MODEL setting: %s - %s",role_model_path,e,)if not is_new:# Get the old status before savingtry:old_instance = ApprovalInstance.objects.get(pk=self.pk)old_status = old_instance.statusexcept ApprovalInstance.DoesNotExist:passsuper().save(*args, **kwargs)if is_new:logger.info("New approval instance created - Flow ID: %s, Step: %s, Status: %s, Assigned to: %s",self.flow.id,self.step_number,self.status,self.assigned_to.username if self.assigned_to else None,)elif old_status and old_status != self.status:logger.info("Approval instance status changed - Flow ID: %s, Step: %s, Old status: %s, New status: %s, Action user: %s",self.flow.id,self.step_number,old_status,self.status,self.action_user.username if self.action_user else None,)else:logger.debug("Approval instance updated - Flow ID: %s, Step: %s",self.flow.id,self.step_number,)

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/966583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go 语言实现简单的文字识别(OCR)

随着人工智能技术的迅猛发展,文字识别(OCR,Optical Character Recognition)已经广泛应用于文档扫描、自动化数据输入等领域。在这篇文章中,我们将通过 Go 语言实现一个简单的文字识别程序,来提取图片中的文字。为…

『回忆录』高二上半期考试

上文:第一次月考。 现在是尚未出分的版本。 月回顾 从上次 1004 开始写,那天晚上也是开始疯狂回忆过去。 然后就是国庆强基三天自习一天。引流:国庆强基 中途 1006 那天晚上前两节课是生日会和中秋节。其中听歌识曲…

多项式牛顿迭代

【前置知识】泰勒展开。设 \(g\) 是一个光滑的函数,\(g(y)=\sum_{n\ge 0} \frac{g^{n}(y_0)}{n!}(y-y_0)^n\).多项式 exp。 给定多项式 \(a(x)\) 满足 \(a_0=0\),求 \(\exp a(x)\bmod x^n\)。 设 \(\exp a(x)=f(x)\)…

轮胎内喷涂优惠工具趋势分析报告

在轮胎制造与橡胶制品生产过程中,如何精准匹配工艺需求与材料性能,始终是行业采购决策中的核心难点。面对市面上琳琅满目的轮胎内喷涂、胶囊隔离剂、胶片隔离剂等产品,用户常因参数混乱、功能描述模糊或缺乏本地化适…

Vibe coding All In One

Vibe coding All In One Vibe coding is an artificial intelligence-assisted software development technique popularized by Andrej Karpathy in February 2025. It was named Collins Dictionarys Word of the Ye…

路径计数与反射容斥

【路径计数模型】 【卡特兰数】 组合意义:从 \((0,0)\) 走到 \((n,n)\),每次向右或者向上,不严格越过对角线的方案数。 它也和长度为 \(2n\) 的合法括号序列个数相等。各种问题都可以转化为卡特兰数。 回忆一下卡特…

多项式复合逆与拉格朗日反演

【定义】 对两多项式 \(f,g\),无常数项且一次项系数非 \(0\),有:\(f(g(x))=x\iff g(f(x))=x\)。(这个结论需要用到高深的群论知识,不会) 如果 \(f(g(x))=x\),称 \(f,g\) 互为复合逆。记 \(f^{-1}\) 为 \(f\) 的…

Day21浮动

1.浮动的基本使用 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-sc…

KEYDIY KD B12-3 3-Button Ford Flip Key Remote - 5pcs/lot (Replacement for Ford Vehicles)

## Problem: Key Replacements for Ford Vehicles – A Costly and Time-Consuming Hassle For European and American automotive repair shops and Ford vehicle owners, replacing lost or damaged keys can feel l…

Spring AI Alibaba 项目源码学习(七)-Agent、BaseAgent、ReactAgent 分析

Agent、BaseAgent、ReactAgent 分析 请关注微信公众号:阿呆-bot 概述 本文档分析 Spring AI Alibaba Agent Framework 中的核心 Agent 类层次结构,包括 Agent 基类、BaseAgent 抽象类和 ReactAgent 具体实现,重点分…

AtCoder Beginner Contest 432 ABCDEG 题目解析

A - Permute to Maximize 题意 给定三个个位数 \(A,B,C\)。 请找出将 \(A,B,C\) 按任意顺序排列并连接起来可以形成的所有三位数中的最大值。 思路 贪心可知,当数字长度一定时,可以优先让高位更大。因此对三个正整数…

fireworks

fireworks https://github.com/materialsproject/fireworks FireWorks stores, executes, and manages calculation workflows.Website (including documentation): https://materialsproject.github.io/fireworks/ He…

KEYDIY KD ZB28-3 Universal Hyundai Smart Remote Key (5pcs/lot) – Reliable Replacement

## Hyundai Smart Key Woes? Meet the KEYDIY KD ZB28-3 Universal Solution ### Problem: The Frustration of Hyundai Smart Key Replacement When a Hyundai owner’s smart remote key fails, or a mechanic need…

Yanhua Mini ACDP-2 A303 Volvo 2022+ IMMO License for ACDP-2 Module20

**Tackling Modern Volvo IMMO Challenges: The Yanhua Mini ACDP-2 A303 License** Modern Volvo vehicles (2022 and newer) are equipped with advanced Immobilizer (IMMO) systems designed to enhance security.…

西电TIC带鱼杯新生训练赛复盘

传送门 A 最大子树和 P1122 最大子树和 - 洛谷几天前看过一眼,大概知道思路,但是因为忘记ans可能取负而没有一次切掉分析题目要求一个节点带权的树的最大子树和我们用 f [ i ]记录子树以节点 i 为根节点时的最大子树…

20251115 - 从零到1详细剖析STM32的CAN架构【以STM32F407为例】

从零到1详细剖析STM32的CAN架构【以STM32F407为例】 1 概览:bxCAN 在 STM32F407 中的位置与作用bxCAN(Basic extended CAN) 是 STM32F4 系列内部实现的 CAN 控制器硬件 IP,用来在物理 CAN 差分总线上收/发 CAN 帧(…

2025.11.15 测试

2025.11.15 测试改题 策略 7:10 顺序开题 T1 随便猜了个结论然后不小心过了大杨利,还好这种题我有经验,没有被吓到 7:49 写完 full_speed 说这个是模板快速读入 因为他写快读但没用,T 到 35pt。。。 T2 额,一眼数…

鸿蒙应用审核被拒?常见原因与避坑指南来了

一个个精心开发的应用,却总在审核关卡折戟,问题究竟出在哪里?鸿蒙生态正迎来前所未有的爆发期。数据显示,HarmonyOS 5 终端数已突破 2300 万,鸿蒙开发者数量超过 800 万,上架应用及元服务超过 3 万款。 在“鸿蒙…

C++篇(13)计算器实现 - 指南

C++篇(13)计算器实现 - 指南2025-11-15 22:58 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importan…

20232306 2025-2026-1 《网络与系统攻防技术》实验五实验报告

1.实验要求 (1)从www.besti.edu.cn、baidu.com、sina.com.cn中选择一个DNS域名进行查询,获取如下信息: DNS注册人及联系方式 该域名对应IP地址 IP地址注册人及联系方式 IP地址所在国家、城市和具体地理位置 PS:使…