/solar/facets.py

https://github.com/joymax/solar · Python · 254 lines · 213 code · 38 blank · 3 comment · 50 complexity · df56b782b213cd9512634669a2351b3b MD5 · raw file

  1. from datetime import datetime, timedelta
  2. from copy import deepcopy
  3. import re
  4. import sys
  5. from util import X, split_param, make_param, process_value
  6. date_formula_re = re.compile('(now)([+\-])([\d]+)d')
  7. def parse_param(arg):
  8. if isinstance(arg, basestring):
  9. m = date_formula_re.match(arg)
  10. if m:
  11. dt, sign, days = m.groups()
  12. dt = datetime.now()
  13. days = int(days)
  14. if sign == '-':
  15. arg = 'NOW/DAY-%sDAYS' % days
  16. else:
  17. arg = 'NOW/DAY+%sDAYS' % days
  18. elif arg is True:
  19. arg = '1'
  20. elif arg is False:
  21. arg = '0'
  22. return arg
  23. def isnull_op(f, v):
  24. if v == '1':
  25. return ~X(**{f: '[* TO *]'})
  26. elif v == '0':
  27. return X(**{f: '[* TO *]'})
  28. return
  29. OPERATORS = {
  30. 'exact': lambda f, v: X(**{f: parse_param(v)}),
  31. 'gte': lambda f, v: X(**{f: '[%s TO *]' % parse_param(v)}),
  32. 'lte': lambda f, v: X(**{f: '[* TO %s]' % parse_param(v)}),
  33. 'isnull': isnull_op,
  34. }
  35. def isnull_op4facet(f, v):
  36. if str(v) == '1':
  37. return '-%s:[* TO *]' % f
  38. elif str(v) == '0':
  39. return '%s:[* TO *]' % f
  40. return
  41. OPS_FOR_FACET = {
  42. 'exact': lambda f, v: '%s:%s' % (f, parse_param(v)),
  43. 'gte': lambda f, v: '%s:[%s TO *]' % (f, parse_param(v)),
  44. 'lte': lambda f, v: '%s:[* TO %s]' % (f, parse_param(v)),
  45. 'isnull': isnull_op4facet,
  46. }
  47. class Facet(object):
  48. model = None
  49. field = None
  50. name = None
  51. title = None
  52. help_text = None
  53. select_multiple = True
  54. default_params = {}
  55. def __init__(self, query, model=None, name=None, title=None, help_text=None):
  56. self.query = query
  57. self.model = model or self.model
  58. self.name = name or self.name
  59. self.title = title or self.title or self.name
  60. self.help_text = help_text or self.help_text
  61. self._values = []
  62. self._selected_values = []
  63. def __iter__(self):
  64. return iter(self.selected_values + self.values)
  65. def __getitem__(self, k):
  66. return self.values[k]
  67. def __unicode__(self):
  68. return unicode(self.title)
  69. def __deepcopy__(self, memo):
  70. facet = self.__class__(self.query, model=self.model, name=self.name, title=self.title, help_text=self.help_text)
  71. facet._values = deepcopy(self._values, memo)
  72. facet._selected_values = deepcopy(self._selected_values, memo)
  73. for new_fv, fv in zip(facet, self):
  74. new_fv.facet = facet
  75. if hasattr(self, '_instance'):
  76. new_fv._instance = self._instance
  77. return facet
  78. def add_value(self, facet_value):
  79. facet_value.facet = self
  80. if facet_value.selected:
  81. self._selected_values.append(facet_value)
  82. else:
  83. self._values.append(facet_value)
  84. @property
  85. def values(self):
  86. return self._values
  87. @property
  88. def selected_values(self):
  89. return self._selected_values
  90. @property
  91. def all_values(self):
  92. return self.selected_values + self.values
  93. @property
  94. def has_selected(self):
  95. return bool(self._selected_values)
  96. def get_instances(self, ids):
  97. if self.model:
  98. return dict([(obj.id, obj) for obj in self.model.query.filter(self.model.id.in_(ids))])
  99. return {}
  100. def get_fv_by_value(self, value):
  101. for fv in self.all_values:
  102. if fv.value == value:
  103. return fv
  104. return None
  105. def _populate_instances(self):
  106. ids = [fv.value for fv in self]
  107. instances = self.get_instances(ids)
  108. for fv in self:
  109. fv._instance = instances.get(fv.value)
  110. def compound_facet_factory(name, title, facets, **attrs):
  111. def _init(self, *args, **kwargs):
  112. super(self.__class__, self).__init__(*args, **kwargs)
  113. self.name = name
  114. def _add_value(self, facet_value):
  115. def fv_cmp(fv1, fv2):
  116. facet_cls1 = self.paramvalue_to_facet_cls_map.get((fv1.param, fv1.value))
  117. facet_cls2 = self.paramvalue_to_facet_cls_map.get((fv2.param, fv2.value))
  118. try:
  119. ix1 = self.facets.index(facet_cls1)
  120. ix2 = self.facets.index(facet_cls2)
  121. ret = cmp(ix1, ix2)
  122. if ret == 0:
  123. return cmp(fv1.count, fv2.count)
  124. return ret
  125. except ValueError:
  126. return 0
  127. facet_cls = self.paramvalue_to_facet_cls_map.get((facet_value.param, facet_value.value))
  128. facet = facet_cls(self.query)
  129. facet.add_value(facet_value)
  130. facet_value._facet = self
  131. if facet_value.selected:
  132. self._selected_values.append(facet_value)
  133. self._selected_values.sort(cmp=fv_cmp)
  134. else:
  135. self._values.append(facet_value)
  136. self._values.sort(cmp=fv_cmp)
  137. attrs['facets'] = facets
  138. attrs['fields'] = [facet.field for facet in facets]
  139. attrs['queries'] = []
  140. attrs['paramvalue_to_facet_cls_map'] = {}
  141. for facet in facets:
  142. if hasattr(facet, 'queries') and facet.queries:
  143. attrs['queries'] += facet.queries
  144. for q in facet.queries:
  145. attrs['paramvalue_to_facet_cls_map'][(make_param(*split_param(q[0][0])), process_value(q[0][1]))] = facet
  146. attrs['name'] = name
  147. attrs['title'] = title
  148. attrs['add_value'] = _add_value
  149. attrs['__init__'] = _init
  150. return type('CompoundFacet', (Facet,), attrs)
  151. class FacetValue(object):
  152. def __init__(self, param, value, count, selected=False, title=None, help_text=None):
  153. self.param = param
  154. self.facet = None
  155. self.value = value
  156. self.count = count
  157. self.selected = selected
  158. self._title = title
  159. self.help_text = help_text
  160. @property
  161. def title(self):
  162. if hasattr(self.facet, 'get_title') and callable(self.facet.get_title):
  163. return self.facet.get_title(self)
  164. if self._title:
  165. return self._title
  166. if hasattr(self.instance, '__unicode__'):
  167. return unicode(self.instance)
  168. if hasattr(self.instance, 'name'):
  169. return self.instance.name
  170. return self.value
  171. def __unicode__(self):
  172. return unicode(self.title)
  173. def __str__(self):
  174. return unicode(self)
  175. def __deepcopy__(self, memo):
  176. fv = self.__class__(self.param, self.value, self.count,
  177. selected=self.selected, title=self._title, help_text=self.help_text)
  178. return fv
  179. def with_count(self):
  180. name = unicode(self)
  181. if self.selected:
  182. return u'%s *' % name
  183. if self.facet and self.facet.has_selected and self.facet.select_multiple:
  184. return u'%s (+%s)' % (name, self.count)
  185. return u'%s (%s)' % (name, self.count)
  186. @property
  187. def instance(self):
  188. if not hasattr(self, '_instance'):
  189. if self.facet:
  190. self.facet._populate_instances()
  191. else:
  192. self._instance = None
  193. return self._instance
  194. @property
  195. def count_sign(self):
  196. if self.facet and self.facet.has_selected and self.facet.select_multiple:
  197. return '+%s' % self.count
  198. return str(self.count)
  199. def get_url(self, *args, **kwargs):
  200. """
  201. Proxy method to facet's get_url method.
  202. """
  203. if hasattr(self.facet, 'get_url') and callable(self.facet.get_url):
  204. return self.facet.get_url(self, *args, **kwargs)
  205. def get_selected_url(self, *args, **kwargs):
  206. """
  207. Proxy method to facet's get_selected_url method.
  208. """
  209. if hasattr(self.facet, 'get_selected_url') and callable(self.facet.get_selected_url):
  210. return self.facet.get_selected_url(self, *args, **kwargs)
  211. # class FacetQueryValue(FacetValue):
  212. # @property
  213. # def param(self):
  214. # return self._param