我构建了一个基于django的小型web应用程序,它利用基本的工作流/状态机概念进行一些操作。没有什么大的或花哨的,所以我最初从未想过使用工作流库来实现它;但是现在,当我需要一些额外的功能时,我正在考虑获得一个能够满足我需求的库,而不是改进我目前所拥有的功能。在
在解释变得太无聊之前,我将简单地解释一下我的需求是什么,以及到目前为止我是如何实现这些需求的。在
虽然我确实使用django,但大多数数据交互都是通过django Rest框架完成的——大多数数据是在客户端通过JavaScript检索和提交的,我认为在服务器端烘焙web表单。在
简而言之,这就是它目前对我的工作方式。在
我的工作流程需要:
简短的问题:随着我的需求和代码库的增长,我的自制实现正在变得过时;有没有一个解决方案可以很好地配合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:我目前的方法可能非常错误
我认为我目前的做法是错误的:
非常感谢你读到这篇文章!在
目前没有回答
相关问题 更多 >
编程相关推荐