Reputation: 867
I am using the Salesforce SOAP API for the first time, and so I'm not familiar with SOAP formatting issues, etc. I am using the lxml
library to generate the XML, but seem to have a formatting issue.
The error I receive is: "The child of the Envelope element must be either a Header or Body element" which is strange, because when I review the XML generated by my SalesforceLeadConverter.build_xml() method, it looks right. This is the
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<Header>
<ns0:SessionHeader xmlns:ns0="urn">
<ns0:sessionId>ldfkjskjdfksdfsdfsdf</ns0:sessionId>
</ns0:SessionHeader>
</Header>
<Body>
<ns1:convertLead xmlns:ns1="urn">
<ns1:leadConverts>
<ns1:leadId>00Qj000000PMV3h</ns1:leadId>
<ns1:doNotCreateOpportunity>False</ns1:doNotCreateOpportunity>
<ns1:sendNotificationEmail>False</ns1:sendNotificationEmail>
</ns1:leadConverts>
</ns1:convertLead>
</Body>
</soapenv:Envelope>
Here is the full class and associated method for generating the XML:
from lxml import etree
class SalesforceLeadConverter(object):
def __init__(self, session_id, lead_id, **kwargs):
""" Provides functionality for converting a Lead to a new or existing
Account and create a new Contact or update an existing Contact.
account_id: Optional; if specified, converts the Lead to a Contact
associated with this Account.
contact_id: Optional; if specified, converts the Lead into an existing
Contact record, preventing the creation of a duplicate.
"""
self.session_id = session_id
self.lead_id = lead_id
self.account_id = kwargs.get('account_id', False)
self.contact_id = kwargs.get('contact_id', False)
self.converted_status = kwargs.get('converted_status', False)
self.do_not_create_opportunity = str(kwargs.get('do_not_create_opportunity', False))
self.opportunity_name = kwargs.get('opportunity_name', False)
self.owner_id = kwargs.get('owner_id', False)
self.send_notification_email = str(kwargs.get('send_notification_email', False))
def build_xml(self):
S_NS = 'http://schemas.xmlsoap.org/soap/envelope/'
S_PRE = '{' + S_NS + '}'
root = etree.Element(S_PRE + 'Envelope', nsmap={'soapenv': S_NS})
soapenv = etree.SubElement(root, 'Header')
header = etree.SubElement(soapenv, '{urn}SessionHeader')
sid = etree.SubElement(header, '{urn}sessionId').text=self.session_id
soapenv2 = etree.SubElement(root, 'Body')
urn2 = etree.SubElement(soapenv2, '{urn}convertLead')
lead_converts = etree.SubElement(urn2, '{urn}leadConverts')
lead_id = etree.SubElement(
lead_converts,
'{urn}leadId'
).text=self.lead_id
do_not_create_opportunity = etree.SubElement(
lead_converts,
'{urn}doNotCreateOpportunity'
).text=self.do_not_create_opportunity
send_notification_email = etree.SubElement(
lead_converts,
'{urn}sendNotificationEmail'
).text=self.send_notification_email
xml_meta = """<?xml version="1.1" encoding="utf-8"?>"""
return xml_meta + etree.tostring(root, encoding='utf-8')
Upvotes: 0
Views: 219
Reputation: 1
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="your_namespace_here" xmlns:ns1="your_namespace_here">
Upvotes: 0
Reputation: 867
The modified SalesforceLeadConverter class below resolved the header/body namespace issue (kudos to MGorgon for pointing me in the right direction). This StackOverflow post ultimately lead me to the right solution.
Note that while the namespace issue was resolved, the code below still doesn't work...there is another issue that popped up, where I'm getting the following error:
"No operation available for request {enterprise.soap.sforce.com}convertLead"
Anyway, here's my soap.py file:
import requests
from lxml.etree import Element, SubElement, tostring
class SalesforceLeadConverter(object):
def __init__(self, session_id, lead_id, **kwargs):
""" Provides functionality for converting a Lead to a new or existing
Account and create a new Contact or update an existing Contact.
account_id: Optional; if specified, converts the Lead to a Contact
associated with this Account.
contact_id: Optional; if specified, converts the Lead into an existing
Contact record, preventing the creation of a duplicate.
"""
self.session_id = session_id
self.lead_id = lead_id
self.account_id = kwargs.get('account_id', False)
self.contact_id = kwargs.get('contact_id', False)
self.converted_status = kwargs.get('converted_status', False)
self.do_not_create_opportunity = str(kwargs.get('do_not_create_opportunity', False))
self.opportunity_name = kwargs.get('opportunity_name', False)
self.owner_id = kwargs.get('owner_id', False)
self.send_notification_email = str(kwargs.get('send_notification_email', False))
def build_xml(self):
schema_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
urn_ns = 'enterprise.soap.sforce.com'
S_PRE = '{' + schema_ns + '}'
envelope = Element(
S_PRE + 'Envelope',
nsmap={'soapenv': schema_ns, 'urn': urn_ns}
)
header = SubElement(envelope, '{%s}Header' % schema_ns)
s_header = SubElement(header, '{%s}SessionHeader' % urn_ns)
sid = SubElement(s_header, '{%s}sessionId' % urn_ns).text=self.session_id
body = SubElement(envelope, '{%s}Body' % schema_ns)
convert_lead = SubElement(body, '{%s}convertLead' % urn_ns)
lead_converts = SubElement(convert_lead, '{%s}leadConverts' % urn_ns)
lead_id = SubElement(
lead_converts,
'{%s}leadId' % urn_ns
).text=self.lead_id
do_not_create_opportunity = SubElement(
lead_converts,
'{%s}doNotCreateOpportunity' % urn_ns
).text=self.do_not_create_opportunity
send_notification_email = SubElement(
lead_converts,
'{%s}sendNotificationEmail' % urn_ns
).text=self.send_notification_email
if self.account_id:
account_id = SubElement(
lead_converts,
'{%s}accountId' % urn_ns
).text=self.account_id
if self.contact_id:
contact_id = SubElement(
lead_converts,
'{%s}contactId' % urn_ns
).text=self.contact_id
if self.converted_status:
converted_status = SubElement(
lead_converts,
'{%s}convertedStatus' % urn_ns
).text=self.converted_status
xml_meta = """<?xml version="1.1" encoding="utf-8"?>"""
return xml_meta + tostring(envelope, encoding='utf-8')
def post(self):
xml = self.build_xml()
headers = {'Content-Type':'text/xml', 'SOAPAction':'convertLead'}
url = 'https://na1.salesforce.com/services/Soap/u/34.0'
out = requests.post(url, data=xml, headers=headers)
return out, out.text
Upvotes: 0
Reputation: 2607
SOAP Body and Header needs a namespace. Also your elements prefixed by ns0, ns1 neads a namespace declaration too.
So your valid SOAP will be:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="your_namespace_here" xmlns:ns1="your_namespace_here">
<soapenv:Header>
<ns0:SessionHeader xmlns:ns0="urn">
<ns0:sessionId>ldfkjskjdfksdfsdfsdf</ns0:sessionId>
</ns0:SessionHeader>
</soapenv:Header>
<soapenv:Body>
<ns1:convertLead xmlns:ns1="urn">
<ns1:leadConverts>
<ns1:leadId>00Qj000000PMV3h</ns1:leadId>
<ns1:doNotCreateOpportunity>False</ns1:doNotCreateOpportunity>
<ns1:sendNotificationEmail>False</ns1:sendNotificationEmail>
</ns1:leadConverts>
</ns1:convertLead>
</soapenv:Body>
</soapenv:Envelope>
Upvotes: 2