如何使用Python/Django中的BonitaSoft REST API

如何使用Python/Django中的BonitaSoft REST API,python,django,rest,Python,Django,Rest,我在网上找不到很多关于如何做这件事的信息,所以我决定发布我是如何做到的。请随时分享您的建议或经验 首先,在settings.py中,我设置了一些可以重用的变量。如果您在本地使用Bonita Studio,则这些是默认设置 BPM_HOST = 'http://localhost:9090/bonita-server-rest/' BPM_USERNAME = 'restuser' BPM_PASSWORD = 'restbpm' 在views.py中,我设置了一个函数,可以在需要调用时随时使用

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

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

BPM_HOST = 'http://localhost:9090/bonita-server-rest/'
BPM_USERNAME = 'restuser'
BPM_PASSWORD = 'restbpm'
在views.py中,我设置了一个函数,可以在需要调用时随时使用。它使用设置文件中的变量,并接受登录用户的参数、要调用的url和post_数据字典。它按照Bonitasoft期望的方式设置基本身份验证和内容类型头

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
现在假设您想要构建一个包含所有流程实例的列表:

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
或构建收件箱中的流程实例列表:

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
发布数据真的很痛苦。首先,我创建了一个函数来清除您可能发送的任何文本:

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

第2部分-添加附件 附加文件需要调用/API/runtimeAPI/addAttachmentOctetStream/{instanceUUID}?name=�&文件名=�

此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
在forms.py中,您需要这样一个表单:

class AddAttachmentForm(Form):
    process_instance_uuid = CharField(widget=HiddenInput())
    attachment = FileField()
#end AddAttachmentForm
在模板中,您需要具有以下内容(请注意enctype):

处理表格时,您应具备:

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'])

第3部分-通过REST调用使用Search Bonitasoft的搜索功能 据我所知,关于这方面的文档很少甚至没有

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

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

你的身体应该是这样的:

options=user:my_user&query=<query_xml>
您可能认为这将返回字段“first_name”设置为“Archibald”的任何记录。事实上,它返回任何包含字段“first_name”(应该是所有字段)的记录,并返回任何值为“Archibald”的用户定义字段。因此,如果某人的姓是“阿奇博尔德”,他们也会被退回。似乎没有办法解决这个问题。最好接受这是一个完整的搜索,并将其简化为:

query.criterion(ProcessInstanceIndex.VARIABLE_VALUE).equalsTo('Archibald');
这将生成以下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>
我不想把分页搞得一团糟,所以如果有超过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'):  
        ...
if request.method==“POST”:
search\u form=SearchForm(request.POST)
如果不是,则搜索表单。表单是否有效()
返回呈现到响应(。。。
#以API预期的方式格式化日期
开始=搜索表单。已清理的数据['start\u date'].strftime(“%Y%m%d”)+“000000000”
end=搜索\表单。已清理\数据['end\ u date'].strftime(“%Y%m%d”)+“99999999”
post_数据={}
#搜索查询必须采用此xml格式
post_数据['query']=''
(
变量_值
%s
或
开始
%s
)
及
开始日期
[%s到%s]
''' \
%(超级清理(搜索表单.清理的数据['search\u text']),
超级清理(搜索表单.清理的数据['search\u text']),
开始
(完)
#获取记录数
response=restcall(请求,'API/queryRuntimeAPI/search',post_数据)
记录的数量=响应。读取()
如果为int(记录数):
记录数=int(记录数)
其他:
返回呈现到响应(。。。
如果记录的数量大于100:
返回呈现到响应(。。。
如果记录的数量=0:
返回呈现到响应(。。。
#现在去拿记录
response=restcall(请求,'API/queryRuntimeAPI/searchByMaxResult?firstResult=0&maxResults=100',post_数据)
root=ET.parse(response).getroot()
#循环处理请求
对于root.findall('LightProcessInstance')中的文档:
...
options=user:my_user&query=<query_xml>
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;
query.criterion(ProcessInstanceIndex.VARIABLE_VALUE).equalsTo('Archibald');
<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>
<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>
<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> 
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"}))
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'):  
        ...