You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

216 lines
6.1 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. from frappe import msgprint, _
  6. import json
  7. import csv
  8. import six
  9. import requests
  10. from six import StringIO, text_type, string_types
  11. from frappe.utils import encode, cstr, cint, flt, comma_or
  12. def read_csv_content_from_uploaded_file(ignore_encoding=False):
  13. if getattr(frappe, "uploaded_file", None):
  14. with open(frappe.uploaded_file, "r") as upfile:
  15. fcontent = upfile.read()
  16. else:
  17. _file = frappe.new_doc("File")
  18. fcontent = _file.get_uploaded_content()
  19. return read_csv_content(fcontent, ignore_encoding)
  20. def read_csv_content_from_attached_file(doc):
  21. fileid = frappe.get_all("File", fields = ["name"], filters = {"attached_to_doctype": doc.doctype,
  22. "attached_to_name":doc.name}, order_by="creation desc")
  23. if fileid : fileid = fileid[0].name
  24. if not fileid:
  25. msgprint(_("File not attached"))
  26. raise Exception
  27. try:
  28. _file = frappe.get_doc("File", fileid)
  29. fcontent = _file.get_content()
  30. return read_csv_content(fcontent, frappe.form_dict.get('ignore_encoding_errors'))
  31. except Exception:
  32. frappe.throw(_("Unable to open attached file. Did you export it as CSV?"), title=_('Invalid CSV Format'))
  33. def read_csv_content(fcontent, ignore_encoding=False):
  34. rows = []
  35. if not isinstance(fcontent, text_type):
  36. decoded = False
  37. for encoding in ["utf-8", "windows-1250", "windows-1252"]:
  38. try:
  39. fcontent = text_type(fcontent, encoding)
  40. decoded = True
  41. break
  42. except UnicodeDecodeError:
  43. continue
  44. if not decoded:
  45. frappe.msgprint(_("Unknown file encoding. Tried utf-8, windows-1250, windows-1252."), raise_exception=True)
  46. fcontent = fcontent.encode("utf-8")
  47. content = [ ]
  48. for line in fcontent.splitlines(True):
  49. if six.PY2:
  50. content.append(line)
  51. else:
  52. content.append(frappe.safe_decode(line))
  53. try:
  54. rows = []
  55. for row in csv.reader(content):
  56. r = []
  57. for val in row:
  58. # decode everything
  59. val = val.strip()
  60. if val=="":
  61. # reason: in maraidb strict config, one cannot have blank strings for non string datatypes
  62. r.append(None)
  63. else:
  64. r.append(val)
  65. rows.append(r)
  66. return rows
  67. except Exception:
  68. frappe.msgprint(_("Not a valid Comma Separated Value (CSV File)"))
  69. raise
  70. @frappe.whitelist()
  71. def send_csv_to_client(args):
  72. if isinstance(args, string_types):
  73. args = json.loads(args)
  74. args = frappe._dict(args)
  75. frappe.response["result"] = cstr(to_csv(args.data))
  76. frappe.response["doctype"] = args.filename
  77. frappe.response["type"] = "csv"
  78. def to_csv(data):
  79. writer = UnicodeWriter()
  80. for row in data:
  81. writer.writerow(row)
  82. return writer.getvalue()
  83. def build_csv_response(data, filename):
  84. frappe.response["result"] = cstr(to_csv(data))
  85. frappe.response["doctype"] = filename
  86. frappe.response["type"] = "csv"
  87. class UnicodeWriter:
  88. def __init__(self, encoding="utf-8"):
  89. self.encoding = encoding
  90. self.queue = StringIO()
  91. self.writer = csv.writer(self.queue, quoting=csv.QUOTE_NONNUMERIC)
  92. def writerow(self, row):
  93. if six.PY2:
  94. row = encode(row, self.encoding)
  95. self.writer.writerow(row)
  96. def getvalue(self):
  97. return self.queue.getvalue()
  98. def check_record(d):
  99. """check for mandatory, select options, dates. these should ideally be in doclist"""
  100. from frappe.utils.dateutils import parse_date
  101. doc = frappe.get_doc(d)
  102. for key in d:
  103. docfield = doc.meta.get_field(key)
  104. val = d[key]
  105. if docfield:
  106. if docfield.reqd and (val=='' or val==None):
  107. frappe.msgprint(_("{0} is required").format(docfield.label), raise_exception=1)
  108. if docfield.fieldtype=='Select' and val and docfield.options:
  109. if val not in docfield.options.split('\n'):
  110. frappe.throw(_("{0} must be one of {1}").format(_(docfield.label), comma_or(docfield.options.split("\n"))))
  111. if val and docfield.fieldtype=='Date':
  112. d[key] = parse_date(val)
  113. elif val and docfield.fieldtype in ["Int", "Check"]:
  114. d[key] = cint(val)
  115. elif val and docfield.fieldtype in ["Currency", "Float", "Percent"]:
  116. d[key] = flt(val)
  117. def import_doc(d, doctype, overwrite, row_idx, submit=False, ignore_links=False):
  118. """import main (non child) document"""
  119. if d.get("name") and frappe.db.exists(doctype, d['name']):
  120. if overwrite:
  121. doc = frappe.get_doc(doctype, d['name'])
  122. doc.flags.ignore_links = ignore_links
  123. doc.update(d)
  124. if d.get("docstatus") == 1:
  125. doc.update_after_submit()
  126. elif d.get("docstatus") == 0 and submit:
  127. doc.submit()
  128. else:
  129. doc.save()
  130. return 'Updated row (#%d) %s' % (row_idx + 1, getlink(doctype, d['name']))
  131. else:
  132. return 'Ignored row (#%d) %s (exists)' % (row_idx + 1,
  133. getlink(doctype, d['name']))
  134. else:
  135. doc = frappe.get_doc(d)
  136. doc.flags.ignore_links = ignore_links
  137. doc.insert()
  138. if submit:
  139. doc.submit()
  140. return 'Inserted row (#%d) %s' % (row_idx + 1, getlink(doctype,
  141. doc.get('name')))
  142. def getlink(doctype, name):
  143. return '<a href="/app/Form/%(doctype)s/%(name)s">%(name)s</a>' % locals()
  144. def get_csv_content_from_google_sheets(url):
  145. # https://docs.google.com/spreadsheets/d/{sheetid}}/edit#gid={gid}
  146. validate_google_sheets_url(url)
  147. # get gid, defaults to first sheet
  148. if "gid=" in url:
  149. gid = url.rsplit('gid=', 1)[1]
  150. else:
  151. gid = 0
  152. # remove /edit path
  153. url = url.rsplit('/edit', 1)[0]
  154. # add /export path,
  155. url = url + '/export?format=csv&gid={0}'.format(gid)
  156. headers = {
  157. 'Accept': 'text/csv'
  158. }
  159. response = requests.get(url, headers=headers)
  160. if response.ok:
  161. # if it returns html, it couldn't find the CSV content
  162. # because of invalid url or no access
  163. if response.text.strip().endswith('</html>'):
  164. frappe.throw(
  165. _('Google Sheets URL is invalid or not publicly accessible.'),
  166. title=_("Invalid URL")
  167. )
  168. return response.content
  169. elif response.status_code == 400:
  170. frappe.throw(_('Google Sheets URL must end with "gid={number}". Copy and paste the URL from the browser address bar and try again.'),
  171. title=_("Incorrect URL"))
  172. else:
  173. response.raise_for_status()
  174. def validate_google_sheets_url(url):
  175. if "docs.google.com/spreadsheets" not in url:
  176. frappe.throw(
  177. _('"{0}" is not a valid Google Sheets URL').format(url),
  178. title=_("Invalid URL"),
  179. )