25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

876 satır
24 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. # IMPORTANT: only import safe functions as this module will be included in jinja environment
  5. import frappe
  6. import operator
  7. import re, urllib, datetime, math, time
  8. import babel.dates
  9. from babel.core import UnknownLocaleError
  10. from dateutil import parser
  11. from num2words import num2words
  12. from six.moves import html_parser as HTMLParser
  13. from six.moves.urllib.parse import quote, urljoin
  14. from html2text import html2text
  15. from markdown2 import markdown, MarkdownError
  16. from six import iteritems, text_type, string_types, integer_types
  17. DATE_FORMAT = "%Y-%m-%d"
  18. TIME_FORMAT = "%H:%M:%S.%f"
  19. DATETIME_FORMAT = DATE_FORMAT + " " + TIME_FORMAT
  20. # datetime functions
  21. def getdate(string_date=None):
  22. """
  23. Coverts string date (yyyy-mm-dd) to datetime.date object
  24. """
  25. if not string_date:
  26. return get_datetime().date()
  27. if isinstance(string_date, datetime.datetime):
  28. return string_date.date()
  29. elif isinstance(string_date, datetime.date):
  30. return string_date
  31. # dateutil parser does not agree with dates like 0000-00-00
  32. if not string_date or string_date=="0000-00-00":
  33. return None
  34. return parser.parse(string_date).date()
  35. def get_datetime(datetime_str=None):
  36. if not datetime_str:
  37. return now_datetime()
  38. if isinstance(datetime_str, (datetime.datetime, datetime.timedelta)):
  39. return datetime_str
  40. elif isinstance(datetime_str, (list, tuple)):
  41. return datetime.datetime(datetime_str)
  42. elif isinstance(datetime_str, datetime.date):
  43. return datetime.datetime.combine(datetime_str, datetime.time())
  44. # dateutil parser does not agree with dates like 0000-00-00
  45. if not datetime_str or (datetime_str or "").startswith("0000-00-00"):
  46. return None
  47. try:
  48. return datetime.datetime.strptime(datetime_str, DATETIME_FORMAT)
  49. except ValueError:
  50. return parser.parse(datetime_str)
  51. def to_timedelta(time_str):
  52. if isinstance(time_str, string_types):
  53. t = parser.parse(time_str)
  54. return datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond)
  55. else:
  56. return time_str
  57. def add_to_date(date, years=0, months=0, days=0, hours=0, as_string=False, as_datetime=False):
  58. """Adds `days` to the given date"""
  59. from dateutil.relativedelta import relativedelta
  60. if date==None:
  61. date = now_datetime()
  62. if hours:
  63. as_datetime = True
  64. if isinstance(date, string_types):
  65. as_string = True
  66. if " " in date:
  67. as_datetime = True
  68. date = parser.parse(date)
  69. date = date + relativedelta(years=years, months=months, days=days, hours=hours)
  70. if as_string:
  71. if as_datetime:
  72. return date.strftime(DATETIME_FORMAT)
  73. else:
  74. return date.strftime(DATE_FORMAT)
  75. else:
  76. return date
  77. def add_days(date, days):
  78. return add_to_date(date, days=days)
  79. def add_months(date, months):
  80. return add_to_date(date, months=months)
  81. def add_years(date, years):
  82. return add_to_date(date, years=years)
  83. def date_diff(string_ed_date, string_st_date):
  84. return (getdate(string_ed_date) - getdate(string_st_date)).days
  85. def time_diff(string_ed_date, string_st_date):
  86. return get_datetime(string_ed_date) - get_datetime(string_st_date)
  87. def time_diff_in_seconds(string_ed_date, string_st_date):
  88. return time_diff(string_ed_date, string_st_date).total_seconds()
  89. def time_diff_in_hours(string_ed_date, string_st_date):
  90. return round(float(time_diff(string_ed_date, string_st_date).total_seconds()) / 3600, 6)
  91. def now_datetime():
  92. dt = convert_utc_to_user_timezone(datetime.datetime.utcnow())
  93. return dt.replace(tzinfo=None)
  94. def get_timestamp(date):
  95. return time.mktime(getdate(date).timetuple())
  96. def get_eta(from_time, percent_complete):
  97. diff = time_diff(now_datetime(), from_time).total_seconds()
  98. return str(datetime.timedelta(seconds=(100 - percent_complete) / percent_complete * diff))
  99. def _get_time_zone():
  100. return frappe.db.get_system_setting('time_zone') or 'Asia/Kolkata'
  101. def get_time_zone():
  102. if frappe.local.flags.in_test:
  103. return _get_time_zone()
  104. return frappe.cache().get_value("time_zone", _get_time_zone)
  105. def convert_utc_to_user_timezone(utc_timestamp):
  106. from pytz import timezone, UnknownTimeZoneError
  107. utcnow = timezone('UTC').localize(utc_timestamp)
  108. try:
  109. return utcnow.astimezone(timezone(get_time_zone()))
  110. except UnknownTimeZoneError:
  111. return utcnow
  112. def now():
  113. """return current datetime as yyyy-mm-dd hh:mm:ss"""
  114. if frappe.flags.current_date:
  115. return getdate(frappe.flags.current_date).strftime(DATE_FORMAT) + " " + \
  116. now_datetime().strftime(TIME_FORMAT)
  117. else:
  118. return now_datetime().strftime(DATETIME_FORMAT)
  119. def nowdate():
  120. """return current date as yyyy-mm-dd"""
  121. return now_datetime().strftime(DATE_FORMAT)
  122. def today():
  123. return nowdate()
  124. def nowtime():
  125. """return current time in hh:mm"""
  126. return now_datetime().strftime(TIME_FORMAT)
  127. def get_first_day(dt, d_years=0, d_months=0):
  128. """
  129. Returns the first day of the month for the date specified by date object
  130. Also adds `d_years` and `d_months` if specified
  131. """
  132. dt = getdate(dt)
  133. # d_years, d_months are "deltas" to apply to dt
  134. overflow_years, month = divmod(dt.month + d_months - 1, 12)
  135. year = dt.year + d_years + overflow_years
  136. return datetime.date(year, month + 1, 1)
  137. def get_last_day(dt):
  138. """
  139. Returns last day of the month using:
  140. `get_first_day(dt, 0, 1) + datetime.timedelta(-1)`
  141. """
  142. return get_first_day(dt, 0, 1) + datetime.timedelta(-1)
  143. def get_time(time_str):
  144. if isinstance(time_str, datetime.datetime):
  145. return time_str.time()
  146. elif isinstance(time_str, datetime.time):
  147. return time_str
  148. else:
  149. if isinstance(time_str, datetime.timedelta):
  150. time_str = str(time_str)
  151. return parser.parse(time_str).time()
  152. def get_datetime_str(datetime_obj):
  153. if isinstance(datetime_obj, string_types):
  154. datetime_obj = get_datetime(datetime_obj)
  155. return datetime_obj.strftime(DATETIME_FORMAT)
  156. def get_user_format():
  157. if getattr(frappe.local, "user_format", None) is None:
  158. frappe.local.user_format = frappe.db.get_default("date_format")
  159. return frappe.local.user_format or "yyyy-mm-dd"
  160. def formatdate(string_date=None, format_string=None):
  161. """
  162. Convers the given string date to :data:`user_format`
  163. User format specified in defaults
  164. Examples:
  165. * dd-mm-yyyy
  166. * mm-dd-yyyy
  167. * dd/mm/yyyy
  168. """
  169. if not string_date:
  170. return ''
  171. date = getdate(string_date)
  172. if not format_string:
  173. format_string = get_user_format().replace("mm", "MM")
  174. try:
  175. formatted_date = babel.dates.format_date(date, format_string, locale=(frappe.local.lang or "").replace("-", "_"))
  176. except UnknownLocaleError:
  177. formatted_date = date.strftime("%Y-%m-%d")
  178. return formatted_date
  179. def format_time(txt):
  180. try:
  181. formatted_time = babel.dates.format_time(get_time(txt), locale=(frappe.local.lang or "").replace("-", "_"))
  182. except UnknownLocaleError:
  183. formatted_time = get_time(txt).strftime("%H:%M:%S")
  184. return formatted_time
  185. def format_datetime(datetime_string, format_string=None):
  186. if not datetime_string:
  187. return
  188. datetime = get_datetime(datetime_string)
  189. if not format_string:
  190. format_string = get_user_format().replace("mm", "MM") + " HH:mm:ss"
  191. try:
  192. formatted_datetime = babel.dates.format_datetime(datetime, format_string, locale=(frappe.local.lang or "").replace("-", "_"))
  193. except UnknownLocaleError:
  194. formatted_datetime = datetime.strftime('%Y-%m-%d %H:%M:%S')
  195. return formatted_datetime
  196. def global_date_format(date):
  197. """returns localized date in the form of January 1, 2012"""
  198. date = getdate(date)
  199. formatted_date = babel.dates.format_date(date, locale=(frappe.local.lang or "en").replace("-", "_"), format="long")
  200. return formatted_date
  201. def has_common(l1, l2):
  202. """Returns truthy value if there are common elements in lists l1 and l2"""
  203. return set(l1) & set(l2)
  204. def flt(s, precision=None):
  205. """Convert to float (ignore commas)"""
  206. if isinstance(s, string_types):
  207. s = s.replace(',','')
  208. try:
  209. num = float(s)
  210. if precision is not None:
  211. num = rounded(num, precision)
  212. except Exception:
  213. num = 0
  214. return num
  215. def cint(s):
  216. """Convert to integer"""
  217. try: num = int(float(s))
  218. except: num = 0
  219. return num
  220. def cstr(s, encoding='utf-8'):
  221. return frappe.as_unicode(s, encoding)
  222. def rounded(num, precision=0):
  223. """round method for round halfs to nearest even algorithm aka banker's rounding - compatible with python3"""
  224. precision = cint(precision)
  225. multiplier = 10 ** precision
  226. # avoid rounding errors
  227. num = round(num * multiplier if precision else num, 8)
  228. floor = math.floor(num)
  229. decimal_part = num - floor
  230. if not precision and decimal_part == 0.5:
  231. num = floor if (floor % 2 == 0) else floor + 1
  232. else:
  233. num = round(num)
  234. return (num / multiplier) if precision else num
  235. def remainder(numerator, denominator, precision=2):
  236. precision = cint(precision)
  237. multiplier = 10 ** precision
  238. if precision:
  239. _remainder = ((numerator * multiplier) % (denominator * multiplier)) / multiplier
  240. else:
  241. _remainder = numerator % denominator
  242. return flt(_remainder, precision);
  243. def round_based_on_smallest_currency_fraction(value, currency, precision=2):
  244. smallest_currency_fraction_value = flt(frappe.db.get_value("Currency",
  245. currency, "smallest_currency_fraction_value"))
  246. if smallest_currency_fraction_value:
  247. remainder_val = remainder(value, smallest_currency_fraction_value, precision)
  248. if remainder_val > (smallest_currency_fraction_value / 2):
  249. value += smallest_currency_fraction_value - remainder_val
  250. else:
  251. value -= remainder_val
  252. else:
  253. value = rounded(value)
  254. return flt(value, precision)
  255. def encode(obj, encoding="utf-8"):
  256. if isinstance(obj, list):
  257. out = []
  258. for o in obj:
  259. if isinstance(o, text_type):
  260. out.append(o.encode(encoding))
  261. else:
  262. out.append(o)
  263. return out
  264. elif isinstance(obj, text_type):
  265. return obj.encode(encoding)
  266. else:
  267. return obj
  268. def parse_val(v):
  269. """Converts to simple datatypes from SQL query results"""
  270. if isinstance(v, (datetime.date, datetime.datetime)):
  271. v = text_type(v)
  272. elif isinstance(v, datetime.timedelta):
  273. v = ":".join(text_type(v).split(":")[:2])
  274. elif isinstance(v, integer_types):
  275. v = int(v)
  276. return v
  277. def fmt_money(amount, precision=None, currency=None):
  278. """
  279. Convert to string with commas for thousands, millions etc
  280. """
  281. number_format = frappe.db.get_default("number_format") or "#,###.##"
  282. if precision is None:
  283. precision = cint(frappe.db.get_default('currency_precision')) or None
  284. decimal_str, comma_str, number_format_precision = get_number_format_info(number_format)
  285. if precision is None:
  286. precision = number_format_precision
  287. # 40,000 -> 40,000.00
  288. # 40,000.00000 -> 40,000.00
  289. # 40,000.23000 -> 40,000.23
  290. if decimal_str:
  291. parts = str(amount).split(decimal_str)
  292. decimals = parts[1] if len(parts) > 1 else ''
  293. if precision > 2:
  294. if len(decimals) < 3:
  295. if currency:
  296. fraction = frappe.db.get_value("Currency", currency, "fraction_units") or 100
  297. precision = len(cstr(fraction)) - 1
  298. else:
  299. precision = number_format_precision
  300. elif len(decimals) < precision:
  301. precision = len(decimals)
  302. amount = '%.*f' % (precision, flt(amount))
  303. if amount.find('.') == -1:
  304. decimals = ''
  305. else:
  306. decimals = amount.split('.')[1]
  307. parts = []
  308. minus = ''
  309. if flt(amount) < 0:
  310. minus = '-'
  311. amount = cstr(abs(flt(amount))).split('.')[0]
  312. if len(amount) > 3:
  313. parts.append(amount[-3:])
  314. amount = amount[:-3]
  315. val = number_format=="#,##,###.##" and 2 or 3
  316. while len(amount) > val:
  317. parts.append(amount[-val:])
  318. amount = amount[:-val]
  319. parts.append(amount)
  320. parts.reverse()
  321. amount = comma_str.join(parts) + ((precision and decimal_str) and (decimal_str + decimals) or "")
  322. amount = minus + amount
  323. if currency and frappe.defaults.get_global_default("hide_currency_symbol") != "Yes":
  324. symbol = frappe.db.get_value("Currency", currency, "symbol") or currency
  325. amount = symbol + " " + amount
  326. return amount
  327. number_format_info = {
  328. "#,###.##": (".", ",", 2),
  329. "#.###,##": (",", ".", 2),
  330. "# ###.##": (".", " ", 2),
  331. "# ###,##": (",", " ", 2),
  332. "#'###.##": (".", "'", 2),
  333. "#, ###.##": (".", ", ", 2),
  334. "#,##,###.##": (".", ",", 2),
  335. "#,###.###": (".", ",", 3),
  336. "#.###": ("", ".", 0),
  337. "#,###": ("", ",", 0)
  338. }
  339. def get_number_format_info(format):
  340. return number_format_info.get(format) or (".", ",", 2)
  341. #
  342. # convet currency to words
  343. #
  344. def money_in_words(number, main_currency = None, fraction_currency=None):
  345. """
  346. Returns string in words with currency and fraction currency.
  347. """
  348. from frappe.utils import get_defaults
  349. _ = frappe._
  350. try:
  351. # note: `flt` returns 0 for invalid input and we don't want that
  352. number = float(number)
  353. except ValueError:
  354. return ""
  355. number = flt(number)
  356. if number < 0:
  357. return ""
  358. d = get_defaults()
  359. if not main_currency:
  360. main_currency = d.get('currency', 'INR')
  361. if not fraction_currency:
  362. fraction_currency = frappe.db.get_value("Currency", main_currency, "fraction") or _("Cent")
  363. number_format = frappe.db.get_value("Currency", main_currency, "number_format", cache=True) or \
  364. frappe.db.get_default("number_format") or "#,###.##"
  365. fraction_length = get_number_format_info(number_format)[2]
  366. n = "%.{0}f".format(fraction_length) % number
  367. numbers = n.split('.')
  368. main, fraction = numbers if len(numbers) > 1 else [n, '00']
  369. if len(fraction) < fraction_length:
  370. zeros = '0' * (fraction_length - len(fraction))
  371. fraction += zeros
  372. in_million = True
  373. if number_format == "#,##,###.##": in_million = False
  374. # 0.00
  375. if main == '0' and fraction in ['00', '000']:
  376. out = "{0} {1}".format(main_currency, _('Zero'))
  377. # 0.XX
  378. elif main == '0':
  379. out = _(in_words(fraction, in_million).title()) + ' ' + fraction_currency
  380. else:
  381. out = main_currency + ' ' + _(in_words(main, in_million).title())
  382. if cint(fraction):
  383. out = out + ' ' + _('and') + ' ' + _(in_words(fraction, in_million).title()) + ' ' + fraction_currency
  384. return out + ' ' + _('only.')
  385. #
  386. # convert number to words
  387. #
  388. def in_words(integer, in_million=True):
  389. """
  390. Returns string in words for the given integer.
  391. """
  392. locale = 'en_IN' if not in_million else frappe.local.lang
  393. integer = int(integer)
  394. try:
  395. ret = num2words(integer, lang=locale)
  396. except NotImplementedError:
  397. ret = num2words(integer, lang='en')
  398. return ret.replace('-', ' ')
  399. def is_html(text):
  400. out = False
  401. for key in ["<br>", "<p", "<img", "<div"]:
  402. if key in text:
  403. out = True
  404. break
  405. return out
  406. def is_image(filepath):
  407. from mimetypes import guess_type
  408. # filepath can be https://example.com/bed.jpg?v=129
  409. filepath = filepath.split('?')[0]
  410. return (guess_type(filepath)[0] or "").startswith("image/")
  411. # from Jinja2 code
  412. _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
  413. def strip_html(text):
  414. """removes anything enclosed in and including <>"""
  415. return _striptags_re.sub("", text)
  416. def escape_html(text):
  417. html_escape_table = {
  418. "&": "&amp;",
  419. '"': "&quot;",
  420. "'": "&apos;",
  421. ">": "&gt;",
  422. "<": "&lt;",
  423. }
  424. return "".join(html_escape_table.get(c,c) for c in text)
  425. def pretty_date(iso_datetime):
  426. """
  427. Takes an ISO time and returns a string representing how
  428. long ago the date represents.
  429. Ported from PrettyDate by John Resig
  430. """
  431. from frappe import _
  432. if not iso_datetime: return ''
  433. import math
  434. if isinstance(iso_datetime, string_types):
  435. iso_datetime = datetime.datetime.strptime(iso_datetime, DATETIME_FORMAT)
  436. now_dt = datetime.datetime.strptime(now(), DATETIME_FORMAT)
  437. dt_diff = now_dt - iso_datetime
  438. # available only in python 2.7+
  439. # dt_diff_seconds = dt_diff.total_seconds()
  440. dt_diff_seconds = dt_diff.days * 86400.0 + dt_diff.seconds
  441. dt_diff_days = math.floor(dt_diff_seconds / 86400.0)
  442. # differnt cases
  443. if dt_diff_seconds < 60.0:
  444. return _('just now')
  445. elif dt_diff_seconds < 120.0:
  446. return _('1 minute ago')
  447. elif dt_diff_seconds < 3600.0:
  448. return _('{0} minutes ago').format(cint(math.floor(dt_diff_seconds / 60.0)))
  449. elif dt_diff_seconds < 7200.0:
  450. return _('1 hour ago')
  451. elif dt_diff_seconds < 86400.0:
  452. return _('{0} hours ago').format(cint(math.floor(dt_diff_seconds / 3600.0)))
  453. elif dt_diff_days == 1.0:
  454. return _('Yesterday')
  455. elif dt_diff_days < 7.0:
  456. return _('{0} days ago').format(cint(dt_diff_days))
  457. elif dt_diff_days < 12:
  458. return _('1 weeks ago')
  459. elif dt_diff_days < 31.0:
  460. return _('{0} weeks ago').format(cint(math.ceil(dt_diff_days / 7.0)))
  461. elif dt_diff_days < 46:
  462. return _('1 month ago')
  463. elif dt_diff_days < 365.0:
  464. return _('{0} months ago').format(cint(math.ceil(dt_diff_days / 30.0)))
  465. elif dt_diff_days < 550.0:
  466. return _('1 year ago')
  467. else:
  468. return '{0} years ago'.format(cint(math.floor(dt_diff_days / 365.0)))
  469. def comma_or(some_list):
  470. return comma_sep(some_list, frappe._("{0} or {1}"))
  471. def comma_and(some_list):
  472. return comma_sep(some_list, frappe._("{0} and {1}"))
  473. def comma_sep(some_list, pattern):
  474. if isinstance(some_list, (list, tuple)):
  475. # list(some_list) is done to preserve the existing list
  476. some_list = [text_type(s) for s in list(some_list)]
  477. if not some_list:
  478. return ""
  479. elif len(some_list) == 1:
  480. return some_list[0]
  481. else:
  482. some_list = ["'%s'" % s for s in some_list]
  483. return pattern.format(", ".join(frappe._(s) for s in some_list[:-1]), some_list[-1])
  484. else:
  485. return some_list
  486. def new_line_sep(some_list):
  487. if isinstance(some_list, (list, tuple)):
  488. # list(some_list) is done to preserve the existing list
  489. some_list = [text_type(s) for s in list(some_list)]
  490. if not some_list:
  491. return ""
  492. elif len(some_list) == 1:
  493. return some_list[0]
  494. else:
  495. some_list = ["%s" % s for s in some_list]
  496. return format("\n ".join(some_list))
  497. else:
  498. return some_list
  499. def filter_strip_join(some_list, sep):
  500. """given a list, filter None values, strip spaces and join"""
  501. return (cstr(sep)).join((cstr(a).strip() for a in filter(None, some_list)))
  502. def get_url(uri=None, full_address=False):
  503. """get app url from request"""
  504. host_name = frappe.local.conf.host_name or frappe.local.conf.hostname
  505. if uri and (uri.startswith("http://") or uri.startswith("https://")):
  506. return uri
  507. if not host_name:
  508. if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
  509. protocol = 'https://' if 'https' == frappe.get_request_header('X-Forwarded-Proto', "") else 'http://'
  510. host_name = protocol + frappe.local.request.host
  511. elif frappe.local.site:
  512. protocol = 'http://'
  513. if frappe.local.conf.ssl_certificate:
  514. protocol = 'https://'
  515. elif frappe.local.conf.wildcard:
  516. domain = frappe.local.conf.wildcard.get('domain')
  517. if domain and frappe.local.site.endswith(domain) and frappe.local.conf.wildcard.get('ssl_certificate'):
  518. protocol = 'https://'
  519. host_name = protocol + frappe.local.site
  520. else:
  521. host_name = frappe.db.get_value("Website Settings", "Website Settings",
  522. "subdomain")
  523. if not host_name:
  524. host_name = "http://localhost"
  525. if host_name and not (host_name.startswith("http://") or host_name.startswith("https://")):
  526. host_name = "http://" + host_name
  527. if not uri and full_address:
  528. uri = frappe.get_request_header("REQUEST_URI", "")
  529. if frappe.conf.http_port:
  530. host_name = host_name + ':' + str(frappe.conf.http_port)
  531. url = urljoin(host_name, uri) if uri else host_name
  532. return url
  533. def get_host_name():
  534. return get_url().rsplit("//", 1)[-1]
  535. def get_link_to_form(doctype, name, label=None):
  536. if not label: label = name
  537. return """<a href="{0}">{1}</a>""".format(get_url_to_form(doctype, name), label)
  538. def get_url_to_form(doctype, name):
  539. return get_url(uri = "desk#Form/{0}/{1}".format(quoted(doctype), quoted(name)))
  540. def get_url_to_list(doctype):
  541. return get_url(uri = "desk#List/{0}".format(quoted(doctype)))
  542. def get_url_to_report(name, report_type = None, doctype = None):
  543. if report_type == "Report Builder":
  544. return get_url(uri = "desk#Report/{0}/{1}".format(quoted(doctype), quoted(name)))
  545. else:
  546. return get_url(uri = "desk#query-report/{0}".format(quoted(name)))
  547. operator_map = {
  548. # startswith
  549. "^": lambda a, b: (a or "").startswith(b),
  550. # in or not in a list
  551. "in": lambda a, b: operator.contains(b, a),
  552. "not in": lambda a, b: not operator.contains(b, a),
  553. # comparison operators
  554. "=": lambda a, b: operator.eq(a, b),
  555. "!=": lambda a, b: operator.ne(a, b),
  556. ">": lambda a, b: operator.gt(a, b),
  557. "<": lambda a, b: operator.lt(a, b),
  558. ">=": lambda a, b: operator.ge(a, b),
  559. "<=": lambda a, b: operator.le(a, b),
  560. "not None": lambda a, b: a and True or False,
  561. "None": lambda a, b: (not a) and True or False
  562. }
  563. def evaluate_filters(doc, filters):
  564. '''Returns true if doc matches filters'''
  565. if isinstance(filters, dict):
  566. for key, value in iteritems(filters):
  567. f = get_filter(None, {key:value})
  568. if not compare(doc.get(f.fieldname), f.operator, f.value):
  569. return False
  570. elif isinstance(filters, (list, tuple)):
  571. for d in filters:
  572. f = get_filter(None, d)
  573. if not compare(doc.get(f.fieldname), f.operator, f.value):
  574. return False
  575. return True
  576. def compare(val1, condition, val2):
  577. ret = False
  578. if condition in operator_map:
  579. ret = operator_map[condition](val1, val2)
  580. return ret
  581. def get_filter(doctype, f):
  582. """Returns a _dict like
  583. {
  584. "doctype":
  585. "fieldname":
  586. "operator":
  587. "value":
  588. }
  589. """
  590. from frappe.model import default_fields, optional_fields
  591. if isinstance(f, dict):
  592. key, value = next(iter(f.items()))
  593. f = make_filter_tuple(doctype, key, value)
  594. if not isinstance(f, (list, tuple)):
  595. frappe.throw(frappe._("Filter must be a tuple or list (in a list)"))
  596. if len(f) == 3:
  597. f = (doctype, f[0], f[1], f[2])
  598. elif len(f) != 4:
  599. frappe.throw(frappe._("Filter must have 4 values (doctype, fieldname, operator, value): {0}").format(str(f)))
  600. f = frappe._dict(doctype=f[0], fieldname=f[1], operator=f[2], value=f[3])
  601. if not f.operator:
  602. # if operator is missing
  603. f.operator = "="
  604. valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "between")
  605. if f.operator.lower() not in valid_operators:
  606. frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators)))
  607. if f.doctype and (f.fieldname not in default_fields + optional_fields):
  608. # verify fieldname belongs to the doctype
  609. meta = frappe.get_meta(f.doctype)
  610. if not meta.has_field(f.fieldname):
  611. # try and match the doctype name from child tables
  612. for df in meta.get_table_fields():
  613. if frappe.get_meta(df.options).has_field(f.fieldname):
  614. f.doctype = df.options
  615. break
  616. return f
  617. def make_filter_tuple(doctype, key, value):
  618. '''return a filter tuple like [doctype, key, operator, value]'''
  619. if isinstance(value, (list, tuple)):
  620. return [doctype, key, value[0], value[1]]
  621. else:
  622. return [doctype, key, "=", value]
  623. def scrub_urls(html):
  624. html = expand_relative_urls(html)
  625. # encoding should be responsibility of the composer
  626. # html = quote_urls(html)
  627. return html
  628. def expand_relative_urls(html):
  629. # expand relative urls
  630. url = get_url()
  631. if url.endswith("/"): url = url[:-1]
  632. def _expand_relative_urls(match):
  633. to_expand = list(match.groups())
  634. if not to_expand[2].startswith('mailto') and not to_expand[2].startswith('data:'):
  635. if not to_expand[2].startswith("/"):
  636. to_expand[2] = "/" + to_expand[2]
  637. to_expand.insert(2, url)
  638. if 'url' in to_expand[0] and to_expand[1].startswith('(') and to_expand[-1].endswith(')'):
  639. # background-image: url('/assets/...') - workaround for wkhtmltopdf print-media-type
  640. to_expand.append(' !important')
  641. return "".join(to_expand)
  642. html = re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?!http)[^\'" >]+)([\'"]?)', _expand_relative_urls, html)
  643. # background-image: url('/assets/...')
  644. html = re.sub('(:[\s]?url)(\([\'"]?)([^\)]*)([\'"]?\))', _expand_relative_urls, html)
  645. return html
  646. def quoted(url):
  647. return cstr(quote(encode(url), safe=b"~@#$&()*!+=:;,.?/'"))
  648. def quote_urls(html):
  649. def _quote_url(match):
  650. groups = list(match.groups())
  651. groups[2] = quoted(groups[2])
  652. return "".join(groups)
  653. return re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?:http)[^\'">]+)([\'"]?)',
  654. _quote_url, html)
  655. def unique(seq):
  656. """use this instead of list(set()) to preserve order of the original list.
  657. Thanks to Stackoverflow: http://stackoverflow.com/questions/480214/how-do-you-remove-duplicates-from-a-list-in-python-whilst-preserving-order"""
  658. seen = set()
  659. seen_add = seen.add
  660. return [ x for x in seq if not (x in seen or seen_add(x)) ]
  661. def strip(val, chars=None):
  662. # \ufeff is no-width-break, \u200b is no-width-space
  663. return (val or "").replace("\ufeff", "").replace("\u200b", "").strip(chars)
  664. def to_markdown(html):
  665. text = None
  666. try:
  667. text = html2text(html)
  668. except HTMLParser.HTMLParseError:
  669. pass
  670. return text
  671. def to_html(markdown_text):
  672. html = None
  673. try:
  674. html = markdown(markdown_text)
  675. except MarkdownError:
  676. pass
  677. return html
  678. def get_source_value(source, key):
  679. '''Get value from source (object or dict) based on key'''
  680. if isinstance(source, dict):
  681. return source.get(key)
  682. else:
  683. return getattr(source, key)