如何使用Python/Django的BonitaSoft REST API

Fra*_*ath 5 python django rest

我在网上找不到关于如何做到这一点的信息,所以我决定发布我是如何做到这一点的.请随时分享您的建议或经验.

首先,在settings.py中,我设置了一些我可以重用的变量.如果您在本地使用Bonita Studio,则这些是默认设置.

BPM_HOST = 'http://localhost:9090/bonita-server-rest/'
BPM_USERNAME = 'restuser'
BPM_PASSWORD = 'restbpm'
Run Code Online (Sandbox Code Playgroud)

在views.py中,我设置了一个我可以随时使用的功能.它使用设置文件中的变量并接受登录用户的参数,要调用的url和post_data的字典.它以Bonitasoft期望的方式设置Basic身份验证和内容类型标头.

from django.conf import settings
import urllib
import urllib2
import base64

def restcall(user,url,post_data={}):
    #create bpm_request
    bpm_request = urllib2.Request(settings.BPM_HOST + url)

    #encode username and password and add to header
    authKey = base64.b64encode(settings.BPM_USERNAME + ':' + settings.BPM_PASSWORD)
    bpm_request.add_header("Authorization","Basic " + authKey)

    #add content type to header
    bpm_request.add_header("Content-Type","application/x-www-form-urlencoded")

    #must send current user in options
    current_user = 'user:' + 'user'
    post_data['options'] = current_user

    bpm_request.add_data(urllib.urlencode(post_data))

    response = urllib2.urlopen(bpm_request)

    try:
        return response
    except Exception, exception: 
        logging.info(str(exception))
    #endtry
#end restcall
Run Code Online (Sandbox Code Playgroud)

现在说你要构建一个所有流程实例的列表:

import xml.etree.ElementTree as ET
response = restcall(my_user,'API/queryRuntimeAPI/getLightProcessInstances') 
root = ET.parse(response).getroot()
UUIDs=[]
for doc in root.findall('LightProcessInstance'):
    UUIDs.append(doc.find('instanceUUID').find('value').text)
#endfor
Run Code Online (Sandbox Code Playgroud)

或者构建收件箱中的流程实例列表:

response = restcall(my_user,'API/queryRuntimeAPI/getLightParentProcessInstancesWithActiveUser/' + my_user +'?fromIndex=0&pageSize=200') 
root = ET.parse(response).getroot()

UUIDs=[]
for doc in root.findall('LightProcessInstance'):
    UUIDs.append(doc.find('instanceUUID').find('value').text)
#endfor
Run Code Online (Sandbox Code Playgroud)

发布数据真的很痛苦.首先,我创建了一个函数来清理你可能发送的任何文本:

def super_clean(text):
    """This will make data safe to send by rest.
    Escape for <> (so we don't screw up our XML).
    Quote plus to handle + signs.
    Encode for international chars and smart quoates.
    Strip to take out extra blanks before and after"""
    return urllib.quote_plus(escape(text.encode('utf-8','replace').strip()))
#end super_clean
Run Code Online (Sandbox Code Playgroud)

以下是我生成XML映射的方法.如果您错过了它,XML地图的文档就在他们的文档中.

variable_string = '<map>' + \
                  '<entry><string>billing_department</string><string>' + super_clean(form.cleaned_data['billing_department']) + '</string></entry>' + \
                  '<entry><string>amount</string><double>' + super_clean(str(form.cleaned_data['amount'])) + '</double></entry>' + \
                  '<entry><string>account</string><string>' + super_clean(str(form.cleaned_data['number_attended'])) + '</long></entry>' + \

variable_string += '<entry><string>participant_name</string><list>'
for tempform in participant_formset.forms:
    if 'participant_name' in tempform.cleaned_data:
        variable_string += '<string>' + super_clean(tempform.cleaned_data['participant_name']) + '</string>'
    #endif
#endfor                  
variable_string += '</list></entry>'

post_data = {}
post_data['variables'] = variable_string
Run Code Online (Sandbox Code Playgroud)

并使用以下内容更新现有记录:

process_instance_uuid = form.cleaned_data['uuid']
response = restcall(request,'API/runtimeAPI/setProcessInstanceVariables/' + process_instance_uuid,post_data)
Run Code Online (Sandbox Code Playgroud)

第2部分 - 添加附件

附加文件需要调用/ API/runtimeAPI/addAttachmentOctetStream/{instanceUUID}?name = &fileName =

这个REST调用与常规的Bonita Rest调用略有不同,所以我编写了以下函数来帮助:

from django.conf import settings
import urllib
import urllib2
import base64

def restAttachment(user,url,file):    
    #create bpm_request
    bpm_request = urllib2.Request(settings.BPM_HOST + url,data=file.read())

    #encode username and password and add to header
    authKey = base64.b64encode(settings.BPM_USERNAME + ':' + settings.BPM_PASSWORD)
    bpm_request.add_header("Authorization","Basic " + authKey)

    #must send current user in options
    current_user = 'user:' + 'user'

    #add headers
    bpm_request.add_header("Content-Type","application/octet-stream")
    bpm_request.add_header('Content-Length', str(file.size))
    bpm_request.add_header('Cache-Control', 'no-cache')
    bpm_request.add_header('options', current_user)
    bpm_request.add_header("Content-Disposition","attachment; filename="+urllib.quote(file.name))

    try:
        response = urllib2.urlopen(bpm_request)
    except Exception, exception: 
        logging.info(str(exception))
    #endtry

    return response
#end restAttachment
Run Code Online (Sandbox Code Playgroud)

在forms.py中,您需要一个像这样的表单:

class AddAttachmentForm(Form):
    process_instance_uuid = CharField(widget=HiddenInput())
    attachment = FileField()
#end AddAttachmentForm
Run Code Online (Sandbox Code Playgroud)

在模板中,您需要具有以下内容(请注意enctype):

<form method="post" enctype="multipart/form-data" action="add_attachment"> {% csrf_token %}
    {{ add_attachment_form }}
    <input type="submit" value="Upload Attachment" />
</form>
Run Code Online (Sandbox Code Playgroud)

在views.py中,构建上述模板时,您应该具有以下内容:

add_attachment_form = AddAttachmentForm(initial={'process_instance_uuid':my_uuid,})
Run Code Online (Sandbox Code Playgroud)

处理表单时,您应该:

if 'attachment' not in request.FILES:
    return render_to_response(...

form = AddAttachmentForm(request.POST, request.FILES) #note the request.FILES
response = restAttachment(user,'API/runtimeAPI/addAttachmentOctetStream/' + form.cleaned_data['process_instance_uuid'] + '?name=bonita_attachment_type_field_name) + '&fileName=' + urllib.quote(request.FILES['attachment'].name),request.FILES['attachment'])
Run Code Online (Sandbox Code Playgroud)

第3部分 - 通过REST调用使用搜索Bonitasoft的搜索功能

据我所知,这方面的文档几乎没有.

让我们从基础开始.要进行搜索,您必须发布到API/queryRuntimeAPI/searchByMaxResult?firstResult = &maxResults = .如果您发布到API/queryRuntimeAPI/search,则只获取记录数,而不是记录本身.

您可以使用firstResult和maxResults进行分页.

你的身体应该是这样的:

options=user:my_user&query=<query_xml>
Run Code Online (Sandbox Code Playgroud)

困难的部分是弄清楚query_xml中的内容.我在Bonitasoft网站上找到的例子都是br标签.出于某种原因,Bonitasoft网站剥离了他们的xml标签.可以使用Java API创建查询xml.一种方法是在Bonita Studio中使用Groovy Editor并使用Evaluate按钮运行它.在以下示例中,"first_name"是我创建的数据字段之一.

import org.ow2.bonita.search.SearchQueryBuilder;
import org.ow2.bonita.search.index.ProcessInstanceIndex;
import org.ow2.bonita.search.index.ActivityInstanceIndex;

SearchQueryBuilder query = new SearchQueryBuilder(new ProcessInstanceIndex());
query.criterion(ProcessInstanceIndex.VARIABLE_NAME).equalsTo("first_name").and().criterion(ProcessInstanceIndex.VARIABLE_VALUE).equalsTo('Archibald');
return query;
Run Code Online (Sandbox Code Playgroud)

你会认为这将返回字段"first_name"设置为"Archibald"的任何记录.实际上,它返回包含字段"first_name"(应该是所有这些字段)的任何记录,并且具有任何用户定义的字段,其值为"Archibald".因此,如果某人的姓氏是"Archibald",他们也将被退回.似乎没有办法解决这个问题.最好只是接受这样一个事实:它是一个完整的搜索并简化为:

query.criterion(ProcessInstanceIndex.VARIABLE_VALUE).equalsTo('Archibald');
Run Code Online (Sandbox Code Playgroud)

这会生成以下XML:

<SearchQueryBuilder>
  <index class="org.ow2.bonita.search.index.ProcessInstanceIndex"/>
  <query>
    <org.ow2.bonita.search.Criterion>
      <builder reference="../../.."/>
      <fieldName>variable_value</fieldName>
      <value>Archibald</value>
    </org.ow2.bonita.search.Criterion>
  </query>
</SearchQueryBuilder>
Run Code Online (Sandbox Code Playgroud)

同样,这只搜索用户定义的字段.如果要搜索其他字段,请参阅http://documentation.bonitasoft.com/javadoc/bpm_engine/5.9/org/ow2/bonita/search/index/ProcessInstanceIndex.html.我也想搜索创建者,所以我的XML变成了:

<SearchQueryBuilder>
<index class="org.ow2.bonita.search.index.ProcessInstanceIndex"/>
        <query>
          <org.ow2.bonita.search.Criterion>
            <builder reference="../../.."/>
            <fieldName>variable_value</fieldName>
            <value>Archibald</value>
          </org.ow2.bonita.search.Criterion>
          <string> OR </string>
          <org.ow2.bonita.search.Criterion>
            <builder reference="../../.."/>
            <fieldName>startedBy</fieldName>
            <value>Archibald</value>
          </org.ow2.bonita.search.Criterion>
        </query>
</SearchQueryBuilder>
Run Code Online (Sandbox Code Playgroud)

最后,我想将搜索范围限制在日期范围内.日期格式为yyyymmddhhmmssttt.我想搜索日期范围,所以我将开始时间设置为0,将结束时间设置为所有9.逻辑规定我在OR子句周围添加括号.

<SearchQueryBuilder>
<index class="org.ow2.bonita.search.index.ProcessInstanceIndex"/>
<query>
        <string>(</string>
        <org.ow2.bonita.search.Criterion>
            <builder reference="../../.."/>
            <fieldName>variable_value</fieldName>
            <value>Archibald</value>
        </org.ow2.bonita.search.Criterion>
        <string> OR </string>
        <org.ow2.bonita.search.Criterion>
            <builder reference="../../.."/>
            <fieldName>startedBy</fieldName>
            <value>Archibald</value>
        </org.ow2.bonita.search.Criterion>
        <string>)</string>
            <string> AND </string>
        <org.ow2.bonita.search.Criterion>
            <builder reference="../../.."/>
            <fieldName>startedDate</fieldName>
            <value>[20130101000000000 TO 20131231999999999]</value>
        </org.ow2.bonita.search.Criterion>
</query>
</SearchQueryBuilder> 
Run Code Online (Sandbox Code Playgroud)

所以现在我有了我的XML,我只需要将它插入Python.我创建了一个表单:

class SearchForm(Form):
    search_text = CharField(widget=TextInput(attrs={'size':'80'}))
    start_date = DateField(widget=widgets.TextInput(attrs={"class":"calendar"}))
    end_date = DateField(widget=widgets.TextInput(attrs={"class":"calendar"}))
Run Code Online (Sandbox Code Playgroud)

我不想弄乱分页,所以如果有超过100条记录,我会给他们一条消息来缩小他们的标准.请注意,在我的xml中,我在搜索文本周围添加了引号.这使搜索匹配整个文本.如果文本中有空格而您不使用引号,则搜索不会尝试匹配所有单词.以下是我处理表单并执行搜索的方法:

if request.method == 'POST':
    search_form = SearchForm(request.POST)

    if not search_form.is_valid():
        return render_to_response(...

    #format dates in manner expected by API
    start = search_form.cleaned_data['start_date'].strftime('%Y%m%d') + "000000000"
    end = search_form.cleaned_data['end_date'].strftime('%Y%m%d') + "999999999"

    post_data={}
    #the search query must be in this xml format
    post_data['query'] = '''
    <SearchQueryBuilder>
      <index class="org.ow2.bonita.search.index.ProcessInstanceIndex"/>
      <query>
        <string>(</string>
        <org.ow2.bonita.search.Criterion>
          <builder reference="../../.."/>
          <fieldName>variable_value</fieldName>
          <value>"%s"</value>
        </org.ow2.bonita.search.Criterion>
        <string> OR </string>
        <org.ow2.bonita.search.Criterion>
          <builder reference="../../.."/>
          <fieldName>startedBy</fieldName>
          <value>"%s"</value>
        </org.ow2.bonita.search.Criterion>
        <string>)</string>
        <string> AND </string>
        <org.ow2.bonita.search.Criterion>
          <builder reference="../../.."/>
          <fieldName>startedDate</fieldName>
          <value>[%s TO %s]</value>
        </org.ow2.bonita.search.Criterion>
      </query>
    </SearchQueryBuilder>''' \
    % (super_clean(search_form.cleaned_data['search_text']),
       super_clean(search_form.cleaned_data['search_text']),
       start,
       end)

    #get number of records
    response = restcall(request,'API/queryRuntimeAPI/search',post_data) 
    number_of_records = response.read()
    if is_int(number_of_records):
        number_of_records = int(number_of_records)
    else:
        return render_to_response(...

    if number_of_records > 100:
        return render_to_response(...

    if number_of_records == 0:
        return render_to_response(...

    #now get the records
    response = restcall(request,'API/queryRuntimeAPI/searchByMaxResult?firstResult=0&maxResults=100',post_data) 
    root = ET.parse(response).getroot()

    #loop through requests
    for doc in root.findall('LightProcessInstance'):  
        ...
Run Code Online (Sandbox Code Playgroud)