filters.py 9.8 KB

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