需要更换Djang的自制工作流引擎

2024-09-28 03:12:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我构建了一个基于django的小型web应用程序,它利用基本的工作流/状态机概念进行一些操作。没有什么大的或花哨的,所以我最初从未想过使用工作流库来实现它;但是现在,当我需要一些额外的功能时,我正在考虑获得一个能够满足我需求的库,而不是改进我目前所拥有的功能。在

在解释变得太无聊之前,我将简单地解释一下我的需求是什么,以及到目前为止我是如何实现这些需求的。在

虽然我确实使用django,但大多数数据交互都是通过django Rest框架完成的——大多数数据是在客户端通过JavaScript检索和提交的,我认为在服务器端烘焙web表单。在

简而言之,这就是它目前对我的工作方式。在

current implementation sequence diagram

我的工作流程需要:

  1. 在创建模型时引入默认字段(配置驱动,而不是模型字段中的默认值)
  2. 引入其他操作(例如,“标记为完成”将保存状态字段设置为“完成”值的实体)
  3. 控制执行操作时更新哪些模型实体字段(例如,在“创建”时,“解决方案”字段将隐藏,“标记为完成”时,解决方案字段将是表单上唯一可显示以供编辑的字段)
  4. 控制用户是否能够根据其角色和/或实体的当前状态执行特定操作(状态为“完成”的实体在可用操作中不会有“标记为完成”)
  5. 使用数据预先填充交互表单(例如,当用户在项目页面上执行“将用户分配到项目”时,“项目”字段将预填充并隐藏在表单上)。在

简短的问题:随着我的需求和代码库的增长,我的自制实现正在变得过时;有没有一个解决方案可以很好地配合Django Rest框架和现有的表单生成器?在

下面是一些额外的无聊细节,有助于更好地了解我的作品目前是如何工作的——我认为这会给问题一个恰当的背景,并导致一个更好的答案。

工作流配置

我的工作流配置以以下格式存储:

#        0               1                      2               3                   4                 5
#('<action_code>'  '<Transition Name>', [<statuses from>], <status to>, [<fields to display>], <Permission getter>)
#Permission getter will return True of False based on the action availability based on user context
wf_Milestone = (
    ('edit', 'Edit', [1, 2, 3, 4], None, '*', 'get_perm_change_status'),
    ('update_scope', 'Update Scope', [1, 2], None, ['scope'], 'get_perm_change_status'),
    ('mark_closed', 'Mark as Closed', [1, 2], 4, [], 'get_perm_change_status'),
    ('mark_cancelled', 'Mark as Cancelled', [1, 2], 3, [], 'get_perm_change_status'),
    ('delete', 'Delete', [1, 2, 3, 4], 5, [], 'get_perm_change_status'),
    ('change_scope_and_delete', 'Update Scope and Delete', [1, 2, 3, 4], 5, ['scope'], 'get_perm_change_status'),
)

基本上,它存储操作的代码和名称(显示在按钮和东西上)、操作有效的状态id的(int)、目标状态的(int)(如果None则不会更改)和模型的方法,以验证用户执行此操作的能力。在

检索可用操作

当检索到模型数据时,它还将把available_actions属性返回给客户机。它分两个阶段完成:首先,将可用的操作转换为类,然后用模型id和其他需要的详细信息填充模型的属性。在

^{pr2}$

当模型实例返回到客户端时,它将如下所示:

[
    {
        "id": 29,
        "author": {
            "first_name": "Sasha",
            "last_name": "Bolotnov",
        },
        "created_date": "Nov 12, 2015 @ 03:11",
        "due_date": null,
        "project": null,
        "status": {
            "id": 1,
            "is_active": true,
            "name": "New",
            "order": 10
        },
        "available_actions": [
            {
                "form_builder_url": "/form_builder/ActionItem/eidt/29/",
                "fields": "*",
                "code": "eidt",
                "name": "Edit",
                "wf_post_url": "/REST/WF/ActionItem/29/eidt/"
            },
            {
                "form_builder_url": "/form_builder/ActionItem/close/29/",
                "fields": [
                    "resolution"
                ],
                "code": "close",
                "name": "Close",
                "wf_post_url": "/REST/WF/ActionItem/29/close/"
            },
            {
                "form_builder_url": "/form_builder/ActionItem/mark_done/29/",
                "fields": [
                    "resolution"
                ],
                "code": "mark_done",
                "name": "Mark as Done",
                "wf_post_url": "/REST/WF/ActionItem/29/mark_done/"
            },
            {
                "form_builder_url": "/form_builder/ActionItem/assign/29/",
                "fields": [
                    "assignee"
                ],
                "code": "assign",
                "name": "Assign",
                "wf_post_url": "/REST/WF/ActionItem/29/assign/"
            },
            {
                "form_builder_url": "/form_builder/ActionItem/delete/29/",
                "fields": [
                    "resolution"
                ],
                "code": "delete",
                "name": "Delete",
                "wf_post_url": "/REST/WF/ActionItem/29/delete/"
            }
        ],
        "chat_id": "ActionItem-29"
    }
]

每个可用的操作都有一个form_builder_url,此URL用于获取用户执行操作所需填写的表单。在

表单生成器

表单生成器生成表单。它确保只有正确的字段可显示,其余字段全部隐藏。它的工作原理很简单。在

我用crispy表单来构建它们:

class ActionItemShortForm(forms.ModelForm):
    assignee = forms.ModelChoiceField(queryset=User.objects.filter(userprofile__allow_login=True).order_by('first_name'))

    class Meta:
        model = ActionItem
        fields = (
            'portfolio',
            'project',
            'priority',
            'due_date',
            'assignee',
            'name',
            'description',
        )

    def __init__(self, *args, **kwargs):
        super(ActionItemShortForm, self).__init__(*args, **kwargs)
        if kwargs.get('initial', {}).get('portfolio'):
            self.fields['project'].queryset = Project.objects.filter(portfolio__id=kwargs.get('initial').get('portfolio'))
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.layout = Layout()

因此,当请求表单时,它使用相同的工作流机制来确定要显示或隐藏的正确字段以及所有:

def get_form(request, action, id=None):

    form = None

    if action == 'create':
        form = ActionItemShortForm(initial=dict(request.GET.iteritems()))
        for param, value in request.GET.iteritems():
            if form.fields.get(param, None):
                form.fields[param].widget = forms.HiddenInput()

    elif action and id:
        _instance = ActionItem.objects.get(id=id)
        wf_definition = WFTransition.get_by_code_from_schema(_instance.get_wf_def(), action)
        if wf_definition:
            form = ActionItemFullForm(instance=_instance)
            if wf_definition.fields == [] or (wf_definition.fields and wf_definition.fields != '*'):
                for field in form.fields:
                    if field not in wf_definition.fields:
                        form.fields[field].widget = forms.HiddenInput()

    return render_to_response('forms/formbase.html', {'form': form}, template.RequestContext(request))

发布表单以启动工作流

当用户填写并提交表单时,数据将进入wf_post_url到工作流处理器中,在需要时,工作流处理器负责管理实体数据、设置其新状态和其他事项:

@api_view(http_method_names=['POST'])
def wf_processor(request, action, id):
    try:
        _instance = ActionItem.objects.get(id=id)
        _wf_def = ActionItem.wf_get_action_by_code(_instance, action)
        if not _wf_def:
            raise Exception("Supplied WF Code %s does not match any of existing WF codes in configuration" % action)
        if _wf_def.fields == [] and len(_wf_def.fields) == 0:
            _instance.status = DicActionItemStatus.objects.get(id=_wf_def.trans_to)
            _instance.save()
        else:
            _serializer = ActionItemBaseSerializer(data=request.data, instance=_instance)
            if _serializer.is_valid():
                if _wf_def.trans_to:
                    _serializer.validated_data[WF_ACTION_ITEM_STATUS_FIELD] = DicActionItemStatus.objects.get(id=_wf_def.trans_to)
                _serializer.save()
            else:
                return Response(_serializer.errors, status=HTTP_400_BAD_REQUEST)
        msg_serializer = MessageSerializer(Message(title="OK", body="Operation completed successfully!"))
        return Response(msg_serializer.data)
    except Exception, e:
        msg_serializer = MessageSerializer(Message(title="Error", body=e.message))
        return Response(msg_serializer.data, status=HTTP_500_INTERNAL_SERVER_ERROR)

上面的内容几乎涵盖了整个周期。它确实管用,尽管我讨厌code并看到重构(我不是一个专业程序员,这可能是我最大的python项目,因此我在不断学习)和优化方面有很大的空间,但是现在有一些事情导致了问题:

问题1:我希望能够将上下文传递到工作流中

假设我正在查看一个带有子对象demand的对象project,并希望创建另一个对象allocation。因为我知道我将针对特定的demand创建分配,所以我能够正确地保存这个分配,但是由于我知道project,所以我希望在表单上预填充并隐藏此字段。换句话说,我希望能够定义哪些附加上下文进入表单生成器并进一步深入。现在,在我当前的实现中,如果我想做到这一点,需要手工维护大量的自定义代码。在

问题2:我目前的方法可能非常错误

我认为我目前的做法是错误的:

  • 它没有封装到一个单一的层中,它与太多的领域有联系:模型、表单、REST
  • 总的来说,有很多代码——大多数实体都有自己的FormBuilder实现(这里有些小东西使得从一个实现中继承几乎是不可能的)
  • 我的代码库看起来不是很可扩展和可维护的——仅仅因为这个原因,我很可能会得到我所拥有的代码库,并用其他东西替换它,即使它不能解决我的第一个问题。在

非常感谢你读到这篇文章!在


Tags: instancenameformidurl表单fieldsget

热门问题