filters.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. # -*- coding: utf-8 -*-
  2. __author__ = 'marcos.medeiros'
  3. from django.db import models
  4. from django.db.models.fields.related import ForeignObjectRel
  5. import datetime
  6. import decimal
  7. import inspect
  8. import locale
  9. from django.template import loader, Context
  10. _filter_templates = 'rapid/filters/'
  11. _filter_url_parameter = 'filter'
  12. class FilterOperator:
  13. def __init__(self, display, query, multiple=False):
  14. self.display = display
  15. self.query = query
  16. self.multiple = multiple
  17. class FilterValue:
  18. available_operations = []
  19. selection_template = _filter_templates + 'key_value.html'
  20. def __init__(self, field, url_value=''):
  21. self.value = None
  22. self.field = field
  23. raise NotImplementedError
  24. def query_value(self):
  25. return self.value
  26. def url_value(self):
  27. return unicode(self.value)
  28. def operator_from_url(self, url_value):
  29. for o in self.available_operations:
  30. if unicode(o.query) == unicode(url_value):
  31. return o
  32. return None
  33. class IntegralFilter(FilterValue):
  34. available_operations = [
  35. FilterOperator('igual a', 'exact'),
  36. FilterOperator('maior que', 'gt'),
  37. FilterOperator('maior ou igual a', 'gte'),
  38. FilterOperator('menor que', 'lt'),
  39. FilterOperator('menor ou igual a', 'lte'),
  40. ]
  41. def __init__(self, field, url_value=0):
  42. self.value = int(url_value)
  43. class BooleanFilter(FilterValue):
  44. available_operations = [
  45. FilterOperator('valor', 'exact'),
  46. ]
  47. def __init__(self, field, url_value='true'):
  48. self.value = bool(url_value)
  49. def __unicode__(self):
  50. return 'true' if self.value else ''
  51. class TextFilter(FilterValue):
  52. available_operations = [
  53. FilterOperator('igual a', 'iexact'),
  54. FilterOperator(u'começa com', 'istartswith'),
  55. FilterOperator('acaba com', 'iendswith'),
  56. FilterOperator('contém', 'icontains'),
  57. ]
  58. def __init__(self, field, url_value=''):
  59. self.value = url_value
  60. def url_value(self):
  61. return self.value if self.value else ''
  62. class DateTimeFilter(FilterValue):
  63. selection_template = _filter_templates + 'date_value.html'
  64. available_operations = [
  65. FilterOperator('igual a', 'exact'),
  66. FilterOperator('maior que', 'gt'),
  67. FilterOperator('maior ou igual a', 'gte'),
  68. FilterOperator('menor que', 'lt'),
  69. FilterOperator('menor ou igual a', 'lte'),
  70. ]
  71. dateformat = '%Y:%m:%d:%H:%M:%S'
  72. def __init__(self, field, url_value=None):
  73. if url_value:
  74. self.value = datetime.datetime.strptime(url_value, self.dateformat)
  75. else:
  76. self.value = datetime.datetime.now()
  77. def url_value(self):
  78. return self.value.strftime(self.dateformat)
  79. class RealFilter(FilterValue):
  80. available_operations = [
  81. FilterOperator('igual a', 'exact'),
  82. FilterOperator('maior que', 'gt'),
  83. FilterOperator('maior ou igual a', 'gte'),
  84. FilterOperator('menor que', 'lt'),
  85. FilterOperator('menor ou igual a', 'lte'),
  86. ]
  87. def __init__(self, field, url_value=0.0):
  88. self.value = decimal.Decimal(url_value)
  89. class TimeFilter(DateTimeFilter):
  90. available_operations = [
  91. FilterOperator('igual a', 'exact'),
  92. FilterOperator('maior que', 'gt'),
  93. FilterOperator('maior ou igual a', 'gte'),
  94. FilterOperator('menor que', 'lt'),
  95. FilterOperator('menor ou igual a', 'lte'),
  96. ]
  97. dateformat = 'HH:MM:SS'
  98. class RelationFilter(FilterValue):
  99. available_operations = [
  100. FilterOperator('igual a', 'exact'),
  101. FilterOperator('na lista', 'in'),
  102. ]
  103. def __init__(self, field, url_value=''):
  104. model = field.related_model()
  105. self.model = model
  106. self.value = [model.default_manager().filter(pk__in=int(v)) for v in url_value.split(':') if v]
  107. def url_value(self):
  108. return ':'.join([str(v.pk) for v in self.value])
  109. field_type_dispatcher = {
  110. models.AutoField: IntegralFilter,
  111. models.BigIntegerField: IntegralFilter,
  112. #models.BinaryField:
  113. #models.BooleanField: BooleanFilter,
  114. models.CharField: TextFilter,
  115. #models.CommaSeparatedIntegerField:
  116. models.DateField: DateTimeFilter,
  117. models.DateTimeField: DateTimeFilter,
  118. #models.DecimalField: RealFilter,
  119. #models.DurationField:TimeFilter,
  120. models.EmailField: TextFilter,
  121. #models.FileField:
  122. #models.FilePathField:
  123. models.FloatField: RealFilter,
  124. #models.ImageField:
  125. models.IntegerField: IntegralFilter,
  126. #models.IPAddressField:
  127. #models.GenericIPAddressField:
  128. #models.NullBooleanField: BooleanFilter,
  129. models.PositiveIntegerField: IntegralFilter,
  130. models.PositiveSmallIntegerField: IntegralFilter,
  131. #models.SlugField:
  132. models.SmallIntegerField: IntegralFilter,
  133. models.TextField: TextFilter,
  134. #models.TimeField: TimeFilter,
  135. #models.URLField:
  136. #models.UUIDField:
  137. #models.ForeignKey: RelationFilter,
  138. #models.ManyToManyField: RelationFilter,
  139. #models.OneToOneField: RelationFilter,
  140. #models.Manager: RelationFilter,
  141. #ForeignObjectRel: RelationFilter,
  142. }
  143. def _get_field_type(field):
  144. tt = inspect.getmro(type(field.field))
  145. for t in tt:
  146. v = field_type_dispatcher.get(t)
  147. if v:
  148. return v
  149. return None
  150. def _get_default_field_value(field):
  151. t = _get_field_type(field)
  152. if t:
  153. return t(field)
  154. return None
  155. def _get_field_value(field, url_value):
  156. value_type = _get_field_type(field)
  157. if value_type:
  158. return value_type(field, url_value)
  159. return None
  160. class Filter:
  161. def __init__(self, field, operator=None, value=None):
  162. self.field = field
  163. if value:
  164. self.value = value
  165. else:
  166. self.value = _get_default_field_value(field)
  167. if operator:
  168. self.operator = operator
  169. else:
  170. self.operator = self.value.available_operations[0]
  171. def query_dict(self):
  172. return self.field.bare_name() + '__' + self.operator.query, self.value.query_value()
  173. def url_para(self):
  174. return self.field.bare_name() + '-' + self.operator.query + '=' + self.value.url_vaue()
  175. @classmethod
  176. def from_request(cls, field, request):
  177. for o in _get_field_type(field).available_operations:
  178. if request.GET.has_key(field.bare_name() + "-" + o.query):
  179. valstr = request.GET[field.bare_name() + "-" + o.query]
  180. val = _get_field_value(field, valstr)
  181. yield Filter(field, o, val)
  182. def selection_value_html(self, request):
  183. c = Context({
  184. 'id': self.field.bare_name() + '_' + self.operator.query,
  185. 'field': self.field,
  186. 'operator': self.operator,
  187. 'default_value': self.value.value,
  188. })
  189. t = loader.get_template(self.value.selection_template)
  190. return t.render(c, request)
  191. @classmethod
  192. def selection_type_html(cls, field, request):
  193. v = _get_field_type(field)
  194. ff = [Filter(field, o) for o in v.available_operations]
  195. ss = [(f.operator, f.selection_value_html(request)) for f in ff]
  196. act = request.get_full_path()
  197. c = Context({
  198. 'field': field,
  199. 'operators': v.available_operations,
  200. 'selectors': ss,
  201. 'action': act,
  202. })
  203. t = loader.get_template(_filter_templates + 'column_selector.html')
  204. return t.render(c)
  205. class FilterSet:
  206. def __init__(self, filters=None):
  207. self.filters = filters if filters else {}
  208. def query_dict(self):
  209. q = []
  210. for ff in self.filters.values():
  211. q += [f.query_dict() for f in ff]
  212. return dict(q)
  213. def url_para(self):
  214. return ';'.join([f.url_para for f in self.filters])
  215. @classmethod
  216. def from_request(cls, model, request):
  217. ff = dict([(f, list(Filter.from_request(f, request))) for f in model.fields() if _get_field_type(f)])
  218. return FilterSet(ff)
  219. @classmethod
  220. def can_filter(cls, field):
  221. return bool(_get_field_type(field))
  222. def render_filters(self, request):
  223. remove_filter_element = '<a class="rapid-remove-filter"><span class="fa fa-times"></span></a>'
  224. ret = ''
  225. kk = self.filters.keys()
  226. kk.sort(key=lambda f: f.name(), cmp=locale.strcoll)
  227. for field in kk:
  228. ret += '<div class="rapid-field-filters %s %s">%s\n' % ('visible' if self.filters[field] else 'hidden',
  229. field.bare_name(), field.name().capitalize())
  230. for f in self.filters[field]:
  231. ret += '<div>' + f.selection_value_html(request) + remove_filter_element + '</div>\n'
  232. ret += '</div>'
  233. return ret
  234. def render_selectors(self, request):
  235. ret = ''
  236. kk = self.filters.keys()
  237. kk.sort(key=lambda f: f.name(), cmp=locale.strcoll)
  238. for field in kk:
  239. ret += '<div class="rapid-filter-selection %s hidden">\n' % field.bare_name()
  240. ret += Filter.selection_type_html(field, request)
  241. ret += '</div>\n'
  242. return ret
  243. def filters_url(self):
  244. pass
  245. def has_filters(self):
  246. for k in self.filters.keys():
  247. if self.filters[k]:
  248. return True
  249. return False