Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

295 рядки
8.5 KiB

  1. import requests
  2. import json
  3. import frappe
  4. '''
  5. FrappeClient is a library that helps you connect with other frappe systems
  6. '''
  7. class AuthError(Exception):
  8. pass
  9. class FrappeException(Exception):
  10. pass
  11. class FrappeClient(object):
  12. def __init__(self, url, username, password, verify=True):
  13. self.verify = verify
  14. self.session = requests.session()
  15. self.url = url
  16. self._login(username, password)
  17. def __enter__(self):
  18. return self
  19. def __exit__(self, *args, **kwargs):
  20. self.logout()
  21. def _login(self, username, password):
  22. '''Login/start a sesion. Called internally on init'''
  23. r = self.session.post(self.url, data={
  24. 'cmd': 'login',
  25. 'usr': username,
  26. 'pwd': password
  27. }, verify=self.verify)
  28. if r.status_code==200 and r.json().get('message') == "Logged In":
  29. return r.json()
  30. else:
  31. raise AuthError
  32. def logout(self):
  33. '''Logout session'''
  34. self.session.get(self.url, params={
  35. 'cmd': 'logout',
  36. }, verify=self.verify)
  37. def get_list(self, doctype, fields='"*"', filters=None, limit_start=0, limit_page_length=0):
  38. """Returns list of records of a particular type"""
  39. if not isinstance(fields, basestring):
  40. fields = json.dumps(fields)
  41. params = {
  42. "fields": fields,
  43. }
  44. if filters:
  45. params["filters"] = json.dumps(filters)
  46. if limit_page_length:
  47. params["limit_start"] = limit_start
  48. params["limit_page_length"] = limit_page_length
  49. res = self.session.get(self.url + "/api/resource/" + doctype, params=params, verify=self.verify)
  50. return self.post_process(res)
  51. def insert(self, doc):
  52. '''Insert a document to the remote server
  53. :param doc: A dict or Document object to be inserted remotely'''
  54. res = self.session.post(self.url + "/api/resource/" + doc.get("doctype"),
  55. data={"data":frappe.as_json(doc)}, verify=self.verify)
  56. return self.post_process(res)
  57. def insert_many(self, docs):
  58. '''Insert multiple documents to the remote server
  59. :param docs: List of dict or Document objects to be inserted in one request'''
  60. return self.post_request({
  61. "cmd": "frappe.client.insert_many",
  62. "docs": frappe.as_json(docs)
  63. })
  64. def update(self, doc):
  65. '''Update a remote document
  66. :param doc: dict or Document object to be updated remotely. `name` is mandatory for this'''
  67. url = self.url + "/api/resource/" + doc.get("doctype") + "/" + doc.get("name")
  68. res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify)
  69. return self.post_process(res)
  70. def bulk_update(self, docs):
  71. '''Bulk update documents remotely
  72. :param docs: List of dict or Document objects to be updated remotely (by `name`)'''
  73. return self.post_request({
  74. "cmd": "frappe.client.bulk_update",
  75. "docs": frappe.as_json(docs)
  76. })
  77. def delete(self, doctype, name):
  78. '''Delete remote document by name
  79. :param doctype: `doctype` to be deleted
  80. :param name: `name` of document to be deleted'''
  81. return self.post_request({
  82. "cmd": "frappe.model.delete_doc",
  83. "doctype": doctype,
  84. "name": name
  85. })
  86. def submit(self, doc):
  87. '''Submit remote document
  88. :param doc: dict or Document object to be submitted remotely'''
  89. return self.post_request({
  90. "cmd": "frappe.client.submit",
  91. "doc": frappe.as_json(doc)
  92. })
  93. def get_value(self, doctype, fieldname=None, filters=None):
  94. '''Returns a value form a document
  95. :param doctype: DocType to be queried
  96. :param fieldname: Field to be returned (default `name`)
  97. :param filters: dict or string for identifying the record'''
  98. return self.get_request({
  99. "cmd": "frappe.client.get_value",
  100. "doctype": doctype,
  101. "fieldname": fieldname or "name",
  102. "filters": frappe.as_json(filters)
  103. })
  104. def set_value(self, doctype, docname, fieldname, value):
  105. '''Set a value in a remote document
  106. :param doctype: DocType of the document to be updated
  107. :param docname: name of the document to be updated
  108. :param fieldname: fieldname of the document to be updated
  109. :param value: value to be updated'''
  110. return self.post_request({
  111. "cmd": "frappe.client.set_value",
  112. "doctype": doctype,
  113. "name": docname,
  114. "fieldname": fieldname,
  115. "value": value
  116. })
  117. def cancel(self, doctype, name):
  118. '''Cancel a remote document
  119. :param doctype: DocType of the document to be cancelled
  120. :param name: name of the document to be cancelled'''
  121. return self.post_request({
  122. "cmd": "frappe.client.cancel",
  123. "doctype": doctype,
  124. "name": name
  125. })
  126. def get_doc(self, doctype, name="", filters=None, fields=None):
  127. '''Returns a single remote document
  128. :param doctype: DocType of the document to be returned
  129. :param name: (optional) `name` of the document to be returned
  130. :param filters: (optional) Filter by this dict if name is not set
  131. :param fields: (optional) Fields to be returned, will return everythign if not set'''
  132. params = {}
  133. if filters:
  134. params["filters"] = json.dumps(filters)
  135. if fields:
  136. params["fields"] = json.dumps(fields)
  137. res = self.session.get(self.url + "/api/resource/" + doctype + "/" + name,
  138. params=params, verify=self.verify)
  139. return self.post_process(res)
  140. def rename_doc(self, doctype, old_name, new_name):
  141. '''Rename remote document
  142. :param doctype: DocType of the document to be renamed
  143. :param old_name: Current `name` of the document to be renamed
  144. :param new_name: New `name` to be set'''
  145. params = {
  146. "cmd": "frappe.client.rename_doc",
  147. "doctype": doctype,
  148. "old_name": old_name,
  149. "new_name": new_name
  150. }
  151. return self.post_request(params)
  152. def migrate_doctype(self, doctype, filters=None, update=None, verbose=1, exclude=None, preprocess=None):
  153. """Migrate records from another doctype"""
  154. meta = frappe.get_meta(doctype)
  155. tables = {}
  156. for df in meta.get_table_fields():
  157. if verbose: print "getting " + df.options
  158. tables[df.fieldname] = self.get_list(df.options, limit_page_length=999999)
  159. # get links
  160. if verbose: print "getting " + doctype
  161. docs = self.get_list(doctype, limit_page_length=999999, filters=filters)
  162. # build - attach children to parents
  163. if tables:
  164. docs = [frappe._dict(doc) for doc in docs]
  165. docs_map = dict((doc.name, doc) for doc in docs)
  166. for fieldname in tables:
  167. for child in tables[fieldname]:
  168. child = frappe._dict(child)
  169. if child.parent in docs_map:
  170. docs_map[child.parent].setdefault(fieldname, []).append(child)
  171. if verbose: print "inserting " + doctype
  172. for doc in docs:
  173. if exclude and doc["name"] in exclude:
  174. continue
  175. if preprocess:
  176. preprocess(doc)
  177. if not doc.get("owner"):
  178. doc["owner"] = "Administrator"
  179. if doctype != "User" and not frappe.db.exists("User", doc.get("owner")):
  180. frappe.get_doc({"doctype": "User", "email": doc.get("owner"),
  181. "first_name": doc.get("owner").split("@")[0] }).insert()
  182. if update:
  183. doc.update(update)
  184. doc["doctype"] = doctype
  185. new_doc = frappe.get_doc(doc)
  186. new_doc.insert()
  187. if not meta.istable:
  188. if doctype != "Communication":
  189. self.migrate_doctype("Communication", {"reference_doctype": doctype, "reference_name": doc["name"]},
  190. update={"reference_name": new_doc.name}, verbose=0)
  191. if doctype != "File":
  192. self.migrate_doctype("File", {"attached_to_doctype": doctype,
  193. "attached_to_name": doc["name"]}, update={"attached_to_name": new_doc.name}, verbose=0)
  194. def migrate_single(self, doctype):
  195. doc = self.get_doc(doctype, doctype)
  196. doc = frappe.get_doc(doc)
  197. # change modified so that there is no error
  198. doc.modified = frappe.db.get_single_value(doctype, "modified")
  199. frappe.get_doc(doc).insert()
  200. def get_api(self, method, params={}):
  201. res = self.session.get(self.url + "/api/method/" + method + "/",
  202. params=params, verify=self.verify)
  203. return self.post_process(res)
  204. def post_api(self, method, params={}):
  205. res = self.session.post(self.url + "/api/method/" + method + "/",
  206. params=params, verify=self.verify)
  207. return self.post_process(res)
  208. def get_request(self, params):
  209. res = self.session.get(self.url, params=self.preprocess(params), verify=self.verify)
  210. res = self.post_process(res)
  211. return res
  212. def post_request(self, data):
  213. res = self.session.post(self.url, data=self.preprocess(data), verify=self.verify)
  214. res = self.post_process(res)
  215. return res
  216. def preprocess(self, params):
  217. """convert dicts, lists to json"""
  218. for key, value in params.iteritems():
  219. if isinstance(value, (dict, list)):
  220. params[key] = json.dumps(value)
  221. return params
  222. def post_process(self, response):
  223. try:
  224. rjson = response.json()
  225. except ValueError:
  226. print response.text
  227. raise
  228. if rjson and ("exc" in rjson) and rjson["exc"]:
  229. raise FrappeException(rjson["exc"])
  230. if 'message' in rjson:
  231. return rjson['message']
  232. elif 'data' in rjson:
  233. return rjson['data']
  234. else:
  235. return None