Skip to content

Commit 4f094d1

Browse files
committed
Fixed merge conflicts.
2 parents 097c620 + 7a557fa commit 4f094d1

4 files changed

Lines changed: 112 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
__pycache__/
33
*.py[cod]
44
*$py.class
5+
.idea/*
56

67
# C extensions
78
*.so

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Changelog
88
* Changed default of TxnSource to None on the following objects: Deposit, Purchase, RefundReceipt, and Transfer.
99
* Changed TxnTaxDetail from a QuickbooksManagedObject to a QuickbooksBaseObject.
1010
* Added a way of disconnecting a Quickbooks Account to client.
11-
* Add support for Quickbooks reports.
11+
* Added support for Quickbooks Reports.
12+
* Added support for Quickbooks Attachments.
1213

1314
* 0.3.13 (May 18, 2016)
1415
* Added option to enable or disable singeton pattern (it defaults to disabled).

quickbooks/client.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import httplib
66
from urlparse import parse_qsl
77

8+
import textwrap
9+
import json
10+
import os
811
from .exceptions import QuickbooksException, SevereException, AuthorizationException
912

1013
try:
@@ -141,18 +144,18 @@ def get_authorize_url(self):
141144
Returns the Authorize URL as returned by QB, and specified by OAuth 1.0a.
142145
:return URI:
143146
"""
144-
self.authorize_url = self.authorize_url[:self.authorize_url.find('?')] if '?' in self.authorize_url else self.authorize_url
147+
self.authorize_url = self.authorize_url[:self.authorize_url.find('?')] \
148+
if '?' in self.authorize_url else self.authorize_url
145149
if self.qbService is None:
146150
self.set_up_service()
147151

148152
response = self.qbService.get_raw_request_token(
149-
params={'oauth_callback': self.callback_url})
153+
params={'oauth_callback': self.callback_url})
150154

151155
oauth_resp = dict(parse_qsl(response.text))
152156

153157
self.request_token = oauth_resp['oauth_token']
154158
self.request_token_secret = oauth_resp['oauth_token_secret']
155-
156159
return self.qbService.get_authorize_url(self.request_token)
157160

158161
def get_report(self, report_type, qs=None):
@@ -200,7 +203,9 @@ def disconnect_account(self):
200203
result = self.make_request("GET", url)
201204
return result
202205

203-
def make_request(self, request_type, url, request_body=None, content_type='application/json', params=None):
206+
def make_request(self, request_type, url, request_body=None, content_type='application/json',
207+
params=None, file_path=None):
208+
204209
if not params:
205210
params = {}
206211

@@ -212,13 +217,46 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
212217

213218
if self.session is None:
214219
self.create_session()
215-
216220
headers = {
217221
'Content-Type': content_type,
218222
'Accept': 'application/json'
219223
}
220224

221-
req = self.session.request(request_type, url, True, self.company_id, headers=headers, params=params, data=request_body)
225+
if file_path:
226+
attachment = open(file_path, 'rb')
227+
url = url.replace('attachable', 'upload')
228+
boundary = '-------------PythonMultipartPost'
229+
headers.update({
230+
'Content-Type': 'multipart/form-data; boundary=%s' % boundary,
231+
'Accept-Encoding': 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
232+
'User-Agent': 'OAuth gem v0.4.7',
233+
'Accept': 'application/json',
234+
'Connection': 'close'
235+
})
236+
237+
binary_data = attachment.read()
238+
239+
request_body = textwrap.dedent(
240+
"""
241+
--%s
242+
Content-Disposition: form-data; name="file_metadata_01"
243+
Content-Type: application/json
244+
245+
%s
246+
247+
--%s
248+
Content-Disposition: form-data; name="file_content_01"
249+
Content-Type: application/pdf
250+
251+
%s
252+
253+
--%s--
254+
"""
255+
) % (boundary, request_body, boundary, binary_data, boundary)
256+
257+
req = self.session.request(
258+
request_type, url, True, self.company_id, headers=headers, params=params, data=request_body)
259+
222260
if req.status_code == httplib.UNAUTHORIZED:
223261
raise AuthorizationException("Application authentication failed", detail=req.text)
224262

@@ -260,11 +298,11 @@ def handle_exceptions(self, results):
260298
else:
261299
raise QuickbooksException(message, code, detail)
262300

263-
def create_object(self, qbbo, request_body):
301+
def create_object(self, qbbo, request_body, _file_path=None):
264302
self.isvalid_object_name(qbbo)
265303

266304
url = self.api_url + "/company/{0}/{1}".format(self.company_id, qbbo.lower())
267-
results = self.make_request("POST", url, request_body)
305+
results = self.make_request("POST", url, request_body, file_path=_file_path)
268306

269307
return results
270308

@@ -280,9 +318,9 @@ def isvalid_object_name(self, object_name):
280318

281319
return True
282320

283-
def update_object(self, qbbo, request_body):
321+
def update_object(self, qbbo, request_body, _file_path=None):
284322
url = self.api_url + "/company/{0}/{1}".format(self.company_id, qbbo.lower())
285-
result = self.make_request("POST", url, request_body)
323+
result = self.make_request("POST", url, request_body, file_path=_file_path)
286324

287325
return result
288326

quickbooks/objects/attachable.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from six import python_2_unicode_compatible
2+
from .base import Ref, QuickbooksManagedObject, QuickbooksTransactionEntity
3+
from ..client import QuickBooks
4+
5+
6+
@python_2_unicode_compatible
7+
class Attachable(QuickbooksManagedObject, QuickbooksTransactionEntity):
8+
"""
9+
QBO definition: This page covers the Attachable, Upload, and Download resources used for attachment management. Attachments are supplemental information linked to a transaction or Item object. They can be files, notes, or a combination of both.
10+
In the case of file attachments, use an upload endpoint multipart request to upload the files to the QuickBooks attachment list and, optionally, to supply metadata for each via an attachable object. If meta data is not supplied with the upload request, the system creates it.
11+
In the case of a note, use the create attachable endpoint.
12+
For information about attachments, see the Attachments Developer Guide.
13+
"""
14+
15+
class_dict = {
16+
"EntityRef": Ref,
17+
}
18+
19+
qbo_object_name = "Attachable"
20+
21+
def __init__(self):
22+
super(Attachable, self).__init__()
23+
24+
self.AttachableRef = []
25+
self.FileName = None
26+
self._FilePath = ''
27+
self.Note = ""
28+
self.FileAccessUri = None
29+
self.TempDownloadUri = None
30+
self.Size = None
31+
self.ContentType = None
32+
self.Category = None
33+
self.Lat = None
34+
self.Long = None
35+
self.PlaceName = None
36+
self.ThumbnailFileAccessUri = None
37+
self.ThumbnailTempDownloadUri = None
38+
39+
def __str__(self):
40+
return self.FileName
41+
42+
def to_ref(self):
43+
ref = Ref()
44+
ref.name = self.FileName
45+
ref.type = self.qbo_object_name
46+
ref.value = self.Id
47+
return ref
48+
49+
def save(self, qb=None):
50+
if not qb:
51+
qb = QuickBooks()
52+
53+
if self.Id and self.Id > 0:
54+
json_data = qb.update_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
55+
else:
56+
json_data = qb.create_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
57+
58+
obj = type(self).from_json(json_data['AttachableResponse'][0]['Attachable'])
59+
self.Id = obj.Id
60+
61+
return obj

0 commit comments

Comments
 (0)