sefaria_tags.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. # -*- coding: utf-8 -*-
  2. """
  3. Custom Sefaria Tags for Django Templates
  4. """
  5. import json
  6. import re
  7. import dateutil.parser
  8. import urllib
  9. import math
  10. from urlparse import urlparse
  11. from django import template
  12. from django.template.defaultfilters import stringfilter
  13. from django.utils.safestring import mark_safe
  14. from django.core.serializers import serialize
  15. from django.db.models.query import QuerySet
  16. from django.contrib.sites.models import Site
  17. from sefaria.sheets import get_sheet
  18. from sefaria.utils.users import user_link as ulink
  19. from sefaria.utils.util import strip_tags as strip_tags_func
  20. from sefaria.utils.hebrew import hebrew_plural, hebrew_term
  21. from sefaria.utils.hebrew import hebrew_term as translate_hebrew_term
  22. import sefaria.model.text
  23. import sefaria.model as m
  24. register = template.Library()
  25. current_site = Site.objects.get_current()
  26. domain = current_site.domain
  27. ref_link_cache = {} # simple cache for ref links
  28. @register.filter(is_safe=True)
  29. @stringfilter
  30. def ref_link(value, absolute=False):
  31. """
  32. Transform a ref into an <a> tag linking to that ref.
  33. e.g. "Genesis 1:3" -> "<a href='/Genesis.1.2'>Genesis 1:2</a>"
  34. """
  35. if value in ref_link_cache:
  36. return ref_link_cache[value]
  37. if not value:
  38. return ""
  39. try:
  40. oref = m.Ref(value)
  41. link = '<a href="/' + oref.url() + '">' + value + '</a>'
  42. except:
  43. link = value
  44. ref_link_cache[value] = mark_safe(link)
  45. return ref_link_cache[value]
  46. he_ref_link_cache = {} # simple cache for ref links
  47. @register.filter(is_safe=True)
  48. @stringfilter
  49. def he_ref_link(value, absolute=False):
  50. """
  51. Transform a ref into an <a> tag linking to that ref in Hebrew.
  52. e.g. "Genesis 1:3" -> "<a href='/Genesis.1.2'>בראשית, א, ב</a>"
  53. """
  54. if value in he_ref_link_cache:
  55. return he_ref_link_cache[value]
  56. if not value:
  57. return ""
  58. try:
  59. oref = m.Ref(value)
  60. link = '<a class="heRef" href="/' + oref.url() + '">' + re.sub(r"\d+(-\d+)?", "", oref.he_normal()) + '</a>'
  61. except:
  62. link = '<a class="heRef" href="#invalid-ref">' + value + '</a>'
  63. he_ref_link_cache[value] = mark_safe(link)
  64. return he_ref_link_cache[value]
  65. @register.filter(is_safe=True)
  66. @stringfilter
  67. def he_ref(value):
  68. """
  69. Returns a Hebrew ref for the english ref passed in.
  70. """
  71. if not value:
  72. return ""
  73. try:
  74. oref = m.Ref(value)
  75. he = oref.he_normal()
  76. except:
  77. he = value
  78. return he
  79. @register.filter(is_safe=True)
  80. @stringfilter
  81. def he_parasha(value):
  82. """
  83. Returns a Hebrew ref for the english ref passed in.
  84. """
  85. if not value:
  86. return ""
  87. def hebrew_parasha(p):
  88. try:
  89. term = m.Term().load({"name": p, "scheme": "Parasha"})
  90. parasha = term.get_titles(lang="he")[0]
  91. except Exception, e:
  92. print e
  93. parasha = p
  94. return parasha
  95. names = value.split("-")
  96. return ("-").join(map(hebrew_parasha, names)) if value != "Lech-Lecha" else hebrew_parasha(value)
  97. @register.filter(is_safe=True)
  98. def version_link(v):
  99. """
  100. Return an <a> tag linking to the first available text of a particular version.
  101. """
  102. try:
  103. section_ref = v.first_section_ref() or v.get_index().nodes.first_leaf().first_section_ref()
  104. except IndexError:
  105. try:
  106. section_ref = v.get_index().nodes.first_leaf().first_section_ref()
  107. except: # Better if we knew how this may fail...
  108. return mark_safe(u'<a href="/{}.1/{}/{}">{}</a>'.format(v.title, v.language, urllib.quote(v.versionTitle.replace(" ", "_").encode("utf-8")), v.versionTitle))
  109. link = u'<a href="/{}/{}/{}">{}</a>'.format(section_ref.url(), v.language, urllib.quote(v.versionTitle.replace(" ", "_").encode("utf-8")), v.versionTitle)
  110. return mark_safe(link)
  111. @register.filter(is_safe=True)
  112. def text_toc_link(indx):
  113. """
  114. Return an <a> tag linking to the text TOC for the Index
  115. """
  116. en = indx.nodes.primary_title("en") if not indx.is_commentary() else indx.title
  117. he = indx.nodes.primary_title("he") if not indx.is_commentary() else indx.heTitle
  118. link = u'''
  119. <a href="/{}">
  120. <span class='en'>{}</span>
  121. <span class='he'>{}</span>
  122. </a>
  123. '''.format(indx.title, en, he)
  124. return mark_safe(link)
  125. @register.filter(is_safe=True)
  126. def person_link(person):
  127. """
  128. Return an <a> tag linking to the first availabe text of a particular version.
  129. """
  130. link = u'''
  131. <a href="/person/{}">
  132. <span class='en'>{}</span>
  133. <span class='he'>{}</span>
  134. </a>
  135. '''.format(person.key, person.primary_name("en"), person.primary_name("he"))
  136. return mark_safe(link)
  137. @register.filter(is_safe=True)
  138. def version_source_link(v):
  139. """
  140. Return an <a> tag linking to the versionSource, or to a Google Search for the source.
  141. """
  142. if " " in v.versionSource or "." not in v.versionSource:
  143. href = "http://www.google.com/search?q=" + urllib.quote(v.versionSource.encode('utf8'))
  144. val = v.versionSource
  145. else:
  146. parsed_uri = urlparse( v.versionSource )
  147. href = v.versionSource
  148. val = parsed_uri.netloc
  149. link = u'<a class="versionSource" href="{}" target="_blank">{}</a>'.format(href, val)
  150. return mark_safe(link)
  151. @register.filter(is_safe=True)
  152. @stringfilter
  153. def url_safe(value):
  154. safe = value.replace(" ", "_")
  155. return mark_safe(safe)
  156. @register.filter(is_safe=True)
  157. def prettify_url(value):
  158. return re.sub(r'^https?:\/\/', '', value, flags=re.MULTILINE)
  159. @register.filter(is_safe=True)
  160. def normalize_url(value):
  161. if re.match(r'^https?:\/\/', value) is None:
  162. value = 'http://' + value
  163. return value
  164. @register.filter(is_safe=True)
  165. def user_link(uid):
  166. return mark_safe(ulink(uid))
  167. @register.filter(is_safe=True)
  168. def lang_code(code):
  169. codes = {
  170. "en": "English",
  171. "he": "Hebrew",
  172. "bi": "Bilingual",
  173. }
  174. return codes.get(code, "Unknown Language")
  175. @register.filter(is_safe=True)
  176. def text_category(text):
  177. """Returns the top level category for text"""
  178. try:
  179. i = m.library.get_index(text)
  180. result = mark_safe(getattr(i, "categories", ["[no cats]"])[0])
  181. except:
  182. result = "[text not found]"
  183. return result
  184. @register.filter(is_safe=True)
  185. def strip_html_entities(text):
  186. text = text if text else ""
  187. text = text.replace("<br>", "\n")
  188. text = text.replace("&amp;", "&")
  189. text = text.replace("&nbsp;", " ")
  190. return mark_safe(text)
  191. @register.filter(is_safe=True)
  192. def strip_tags(value):
  193. """
  194. Returns the given HTML with all tags stripped.
  195. """
  196. return mark_safe(strip_tags_func(value))
  197. @register.filter(is_safe=True)
  198. @stringfilter
  199. def sheet_link(value):
  200. """
  201. Returns a link to sheet with id value.
  202. """
  203. value = int(value)
  204. sheet = get_sheet(value)
  205. if "error" in sheet:
  206. safe = "<a href='#'>[sheet not found]</a>"
  207. else:
  208. safe = "<a href='/sheets/%d' data-id='%d'>%s</a>" % (value, value, strip_tags_func(sheet["title"]))
  209. return mark_safe(safe)
  210. @register.filter(is_safe=True)
  211. def discussion_link(discussion):
  212. """
  213. Returns a link to layer with id value.
  214. :param discussion is either a Layer object or a urlkey for a Layer object.
  215. """
  216. if isinstance(discussion, basestring):
  217. discussion = m.Layer().load({"urlkey": discussion})
  218. if not discussion:
  219. return mark_safe("[discusion not found]")
  220. if getattr(discussion, "first_ref", None):
  221. oref = m.Ref(discussion.first_ref)
  222. href = "/" + oref.url() + "?layer=" + discussion.urlkey
  223. count = len(discussion.note_ids)
  224. safe = "<a href='{}'>{} ({} notes)</a>".format(href, oref.normal(), count)
  225. else:
  226. safe = "<a href='/Genesis.1?layer=" + discussion.urlkey + "'>Unstarted Discussion</a>"
  227. return mark_safe(safe)
  228. @register.filter(is_safe=True)
  229. def absolute_link(value):
  230. """
  231. Takes a string with a single <a> tag a replaces the href with absolute URL.
  232. <a href='/Job.3.4'>Job 3:4</a> --> <a href='http://www.sefaria.org/Job.3.4'>Job 3:4</a>
  233. """
  234. # run twice to account for either single or double quotes
  235. absolute = value.replace("href='/", "href='http://%s/" % domain)
  236. absolute = absolute.replace('href="/', 'href="http://%s/' % domain)
  237. return mark_safe(absolute)
  238. @register.filter(is_safe=True)
  239. def license_link(value):
  240. """
  241. Returns the text of an <a> tag linking to a page explaining a license.
  242. """
  243. links = {
  244. "Public Domain": "http://en.wikipedia.org/wiki/Public_domain",
  245. "CC0": "http://creativecommons.org/publicdomain/zero/1.0/",
  246. "CC-BY": "http://creativecommons.org/licenses/by/3.0/",
  247. "CC-BY-SA": "http://creativecommons.org/licenses/by-sa/3.0/",
  248. }
  249. if value not in links:
  250. return mark_safe(value)
  251. return mark_safe("<a href='%s' target='_blank'>%s</a>" % (links[value], value))
  252. @register.filter(is_safe=True)
  253. @stringfilter
  254. def trim_title(value):
  255. safe = value.replace("Mishneh Torah, ", "")
  256. safe = safe.replace("Shulchan Arukh, ", "")
  257. safe = safe.replace("Jerusalem Talmud ", "")
  258. safe = safe.replace(u"משנה תורה, ", "")
  259. return mark_safe(safe)
  260. @register.filter(is_safe=True)
  261. @stringfilter
  262. def abbreviate_number(value):
  263. """
  264. 13,324,4234 -> 13M
  265. 35,234 -> 35k
  266. 231,421,412,432 - 231B
  267. """
  268. try:
  269. n = int(value)
  270. except:
  271. return mark_safe(value)
  272. if n > 1000000000:
  273. abbr = "%dB" % ( n / 1000000000 )
  274. elif n > 1000000:
  275. abbr = "%dM" % ( n / 1000000 )
  276. elif n > 1000:
  277. abbr = "%dk" % ( n / 1000 )
  278. else:
  279. abbr = str(n)
  280. return mark_safe(abbr)
  281. @register.filter(is_safe=True)
  282. def sum_counts(counts):
  283. return sum(counts.values()) / 570.0
  284. @register.filter(is_safe=True)
  285. def percent_available(array, key):
  286. return array[key]["percentAvailable"]
  287. @register.filter(is_safe=True)
  288. def pluralize(value):
  289. """
  290. Hebrew friendly plurals
  291. """
  292. return mark_safe(hebrew_plural(value))
  293. @register.filter(is_safe=True)
  294. def hebrew_term(value):
  295. """
  296. Hebrew friendly plurals
  297. """
  298. return mark_safe(translate_hebrew_term(value))
  299. @register.filter(is_safe=True)
  300. def jsonify(object):
  301. if isinstance(object, QuerySet):
  302. return mark_safe(serialize('json', object))
  303. return mark_safe(json.dumps(object))
  304. @register.simple_tag
  305. def get_private_attribute(model_instance, attrib_name):
  306. return getattr(model_instance, attrib_name, '')
  307. @register.filter(is_safe=True)
  308. def nice_timestamp(timestamp):
  309. return dateutil.parser.parse(timestamp).strftime("%m/%d/%y")
  310. # Derived from https://djangosnippets.org/snippets/6/
  311. """
  312. Template tags for working with lists.
  313. You'll use these in templates thusly::
  314. {% load listutil %} # I don't think we need this line.
  315. {% for sublist in mylist|parition:"3" %}
  316. {% for item in mylist %}
  317. do something with {{ item }}
  318. {% endfor %}
  319. {% endfor %}
  320. """
  321. @register.filter
  322. def partition_into(thelist, n):
  323. """
  324. Break a list into ``n`` pieces. The last list may be larger than the rest if
  325. the list doesn't break cleanly. That is::
  326. >>> l = range(10)
  327. >>> partition(l, 2)
  328. [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
  329. >>> partition(l, 3)
  330. [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
  331. >>> partition(l, 4)
  332. [[0, 1], [2, 3], [4, 5], [6, 7, 8, 9]]
  333. >>> partition(l, 5)
  334. [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
  335. """
  336. try:
  337. n = int(n)
  338. thelist = list(thelist)
  339. except (ValueError, TypeError):
  340. return [thelist]
  341. p = len(thelist) / n
  342. return [thelist[p*i:p*(i+1)] for i in range(n - 1)] + [thelist[p*(i+1):]]
  343. @register.filter
  344. def partition_by(thelist, n):
  345. """
  346. Break a list into ``n`` sized peices
  347. ``partition_by(range(10), 3)`` gives::
  348. [[1, 4, 7],
  349. [2, 5, 8],
  350. [3, 6, 9],
  351. [10]]
  352. """
  353. try:
  354. n = int(n)
  355. thelist = list(thelist)
  356. except (ValueError, TypeError):
  357. return [thelist]
  358. rows = int(math.ceil(float(len(thelist)) / n))
  359. newlists = [thelist[r * n : (r + 1) * n] for r in range(rows)]
  360. return newlists
  361. @register.filter
  362. def partition_vertical(thelist, n):
  363. """
  364. Break a list into ``n`` peices, but "horizontally." That is,
  365. ``partition_horizontal(range(10), 3)`` gives::
  366. [[1, 4, 7],
  367. [2, 5, 8],
  368. [3, 6, 9],
  369. [10]]
  370. Clear as mud?
  371. """
  372. try:
  373. n = int(n)
  374. thelist = list(thelist)
  375. except (ValueError, TypeError):
  376. return [thelist]
  377. newlists = [list() for i in range(n)]
  378. for i, val in enumerate(thelist):
  379. newlists[i%n].append(val)
  380. return newlists