filters.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. available_operations = [
  64. FilterOperator('igual a', 'exact'),
  65. FilterOperator('maior que', 'gt'),
  66. FilterOperator('maior ou igual a', 'gte'),
  67. FilterOperator('menor que', 'lt'),
  68. FilterOperator('menor ou igual a', 'lte'),
  69. ]
  70. dateformat = '%Y:%m:%d:%H:%M:%S'
  71. def __init__(self, field, url_value=None):
  72. if url_value:
  73. self.value = datetime.datetime.strptime(url_value, self.dateformat)
  74. else:
  75. self.value = datetime.datetime.now()
  76. def url_value(self):
  77. return self.value.strftime(self.dateformat)
  78. class RealFilter(FilterValue):
  79. available_operations = [
  80. FilterOperator('igual a', 'exact'),
  81. FilterOperator('maior que', 'gt'),
  82. FilterOperator('maior ou igual a', 'gte'),
  83. FilterOperator('menor que', 'lt'),
  84. FilterOperator('menor ou igual a', 'lte'),
  85. ]
  86. def __init__(self, field, url_value=0.0):
  87. self.value = decimal.Decimal(url_value)
  88. class TimeFilter(DateTimeFilter):
  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. dateformat = 'HH:MM:SS'
  97. class RelationFilter(FilterValue):
  98. available_operations = [
  99. FilterOperator('igual a', 'exact'),
  100. FilterOperator('na lista', 'in'),
  101. ]
  102. def __init__(self, field, url_value=''):
  103. model = field.related_model()
  104. self.model = model
  105. self.value = [model.default_manager().filter(pk__in=int(v)) for v in url_value.split(':') if v]
  106. def url_value(self):
  107. return ':'.join([str(v.pk) for v in self.value])
  108. field_type_dispatcher = {
  109. models.AutoField: IntegralFilter,
  110. models.BigIntegerField: IntegralFilter,
  111. #models.BinaryField:
  112. #models.BooleanField: BooleanFilter,
  113. models.CharField: TextFilter,
  114. #models.CommaSeparatedIntegerField:
  115. #models.DateField: DateTimeFilter,
  116. #models.DateTimeField: DateTimeFilter,
  117. #models.DecimalField: RealFilter,
  118. #models.DurationField:TimeFilter,
  119. models.EmailField: TextFilter,
  120. #models.FileField:
  121. #models.FilePathField:
  122. models.FloatField: RealFilter,
  123. #models.ImageField:
  124. models.IntegerField: IntegralFilter,
  125. #models.IPAddressField:
  126. #models.GenericIPAddressField:
  127. #models.NullBooleanField: BooleanFilter,
  128. models.PositiveIntegerField: IntegralFilter,
  129. models.PositiveSmallIntegerField: IntegralFilter,
  130. #models.SlugField:
  131. models.SmallIntegerField: IntegralFilter,
  132. models.TextField: TextFilter,
  133. #models.TimeField: TimeFilter,
  134. #models.URLField:
  135. #models.UUIDField:
  136. #models.ForeignKey: RelationFilter,
  137. #models.ManyToManyField: RelationFilter,
  138. #models.OneToOneField: RelationFilter,
  139. #models.Manager: RelationFilter,
  140. #ForeignObjectRel: RelationFilter,
  141. }
  142. def _get_field_type(field):
  143. tt = inspect.getmro(type(field.field))
  144. for t in tt:
  145. v = field_type_dispatcher.get(t)
  146. if v:
  147. return v
  148. return None
  149. def _get_default_field_value(field):
  150. t = _get_field_type(field)
  151. if t:
  152. return t(field)
  153. return None
  154. def _get_field_value(field, url_value):
  155. value_type = _get_field_type(field)
  156. if value_type:
  157. return value_type(field, url_value)
  158. return None
  159. class Filter:
  160. def __init__(self, field, operator=None, value=None):
  161. self.field = field
  162. if value:
  163. self.value = value
  164. else:
  165. self.value = _get_default_field_value(field)
  166. if operator:
  167. self.operator = operator
  168. else:
  169. self.operator = self.value.available_operations[0]
  170. def query_dict(self):
  171. return self.field.bare_name() + '__' + self.operator.query, self.value.query_value()
  172. def url_para(self):
  173. return self.field.bare_name() + '-' + self.operator.query + '=' + self.value.url_vaue()
  174. @classmethod
  175. def from_request(cls, field, request):
  176. for o in _get_field_type(field).available_operations:
  177. if request.GET.has_key(field.bare_name() + "-" + o.query):
  178. valstr = request.GET[field.bare_name() + "-" + o.query]
  179. val = _get_field_value(field, valstr)
  180. yield Filter(field, o, val)
  181. def selection_value_html(self, request):
  182. c = Context({
  183. 'id': self.field.bare_name() + '_' + self.operator.query,
  184. 'field': self.field,
  185. 'operator': self.operator,
  186. 'default_value': self.value.value,
  187. })
  188. t = loader.get_template(self.value.selection_template)
  189. return t.render(c, request)
  190. @classmethod
  191. def selection_type_html(cls, field, request):
  192. v = _get_field_type(field)
  193. ff = [Filter(field, o) for o in v.available_operations]
  194. ss = [(f.operator, f.selection_value_html(request)) for f in ff]
  195. act = request.get_full_path()
  196. c = Context({
  197. 'field': field,
  198. 'operators': v.available_operations,
  199. 'selectors': ss,
  200. 'action': act,
  201. })
  202. t = loader.get_template(_filter_templates + 'column_selector.html')
  203. return t.render(c)
  204. class FilterSet:
  205. def __init__(self, filters=None):
  206. self.filters = filters if filters else {}
  207. def query_dict(self):
  208. q = []
  209. for ff in self.filters.values():
  210. q += [f.query_dict() for f in ff]
  211. return dict(q)
  212. def url_para(self):
  213. return ';'.join([f.url_para for f in self.filters])
  214. @classmethod
  215. def from_request(cls, model, request):
  216. ff = dict([(f, list(Filter.from_request(f, request))) for f in model.fields() if _get_field_type(f)])
  217. return FilterSet(ff)
  218. @classmethod
  219. def can_filter(cls, field):
  220. return bool(_get_field_type(field))
  221. def render_filters(self, request):
  222. remove_filter_element = '<a class="rapid-remove-filter"><span class="fa fa-times"></span></a>'
  223. ret = ''
  224. kk = self.filters.keys()
  225. kk.sort(key=lambda f: f.name(), cmp=locale.strcoll)
  226. for field in kk:
  227. ret += '<div class="rapid-field-filters %s %s">%s\n' % ('visible' if self.filters[field] else 'hidden',
  228. field.bare_name(), field.name().capitalize())
  229. for f in self.filters[field]:
  230. ret += '<div>' + f.selection_value_html(request) + remove_filter_element + '</div>\n'
  231. ret += '</div>'
  232. return ret
  233. def render_selectors(self, request):
  234. ret = ''
  235. kk = self.filters.keys()
  236. kk.sort(key=lambda f: f.name(), cmp=locale.strcoll)
  237. for field in kk:
  238. ret += '<div class="rapid-filter-selection %s hidden">\n' % field.bare_name()
  239. ret += Filter.selection_type_html(field, request)
  240. ret += '</div>\n'
  241. return ret
  242. def filters_url(self):
  243. pass
  244. def has_filters(self):
  245. for k in self.filters.keys():
  246. if self.filters[k]:
  247. return True
  248. return False