Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

295 wiersze
8.6 KiB

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