1import time 2import datetime 3import uuid 4 5from flask_admin.babel import lazy_gettext 6 7 8class BaseFilter(object): 9 """ 10 Base filter class. 11 """ 12 def __init__(self, name, options=None, data_type=None, key_name=None): 13 """ 14 Constructor. 15 16 :param name: 17 Displayed name 18 :param options: 19 List of fixed options. If provided, will use drop down instead of textbox. 20 :param data_type: 21 Client-side widget type to use. 22 :param key_name: 23 Optional name who represent this filter. 24 """ 25 self.name = name 26 self.options = options 27 self.data_type = data_type 28 self.key_name = key_name 29 30 def get_options(self, view): 31 """ 32 Return list of predefined options. 33 34 Override to customize behavior. 35 36 :param view: 37 Associated administrative view class. 38 """ 39 options = self.options 40 41 if options: 42 if callable(options): 43 options = options() 44 45 return options 46 47 return None 48 49 def validate(self, value): 50 """ 51 Validate value. 52 53 If value is valid, returns `True` and `False` otherwise. 54 55 :param value: 56 Value to validate 57 """ 58 # useful for filters with date conversions, see if conversion in clean() raises ValueError 59 try: 60 self.clean(value) 61 return True 62 except ValueError: 63 return False 64 65 def clean(self, value): 66 """ 67 Parse value into python format. Occurs before .apply() 68 69 :param value: 70 Value to parse 71 """ 72 return value 73 74 def apply(self, query, value): 75 """ 76 Apply search criteria to the query and return new query. 77 78 :param query: 79 Query 80 :param value: 81 Search criteria 82 """ 83 raise NotImplementedError() 84 85 def operation(self): 86 """ 87 Return readable operation name. 88 89 For example: u'equals' 90 """ 91 raise NotImplementedError() 92 93 def __unicode__(self): 94 return self.name 95 96 97# Customized filters 98class BaseBooleanFilter(BaseFilter): 99 """ 100 Base boolean filter, uses fixed list of options. 101 """ 102 def __init__(self, name, options=None, data_type=None): 103 super(BaseBooleanFilter, self).__init__(name, 104 (('1', lazy_gettext(u'Yes')), 105 ('0', lazy_gettext(u'No'))), 106 data_type) 107 108 def validate(self, value): 109 return value in ('0', '1') 110 111 112class BaseIntFilter(BaseFilter): 113 """ 114 Base Int filter. Adds validation and changes value to python int. 115 116 Avoid using int(float(value)) to also allow using decimals, because it 117 causes precision issues with large numbers. 118 """ 119 def clean(self, value): 120 return int(value) 121 122 123class BaseFloatFilter(BaseFilter): 124 """ 125 Base Float filter. Adds validation and changes value to python float. 126 """ 127 def clean(self, value): 128 return float(value) 129 130 131class BaseIntListFilter(BaseFilter): 132 """ 133 Base Integer list filter. Adds validation for int "In List" filter. 134 135 Avoid using int(float(value)) to also allow using decimals, because it 136 causes precision issues with large numbers. 137 """ 138 def clean(self, value): 139 return [int(v.strip()) for v in value.split(',') if v.strip()] 140 141 142class BaseFloatListFilter(BaseFilter): 143 """ 144 Base Float list filter. Adds validation for float "In List" filter. 145 """ 146 def clean(self, value): 147 return [float(v.strip()) for v in value.split(',') if v.strip()] 148 149 150class BaseDateFilter(BaseFilter): 151 """ 152 Base Date filter. Uses client-side date picker control. 153 """ 154 def __init__(self, name, options=None, data_type=None): 155 super(BaseDateFilter, self).__init__(name, 156 options, 157 data_type='datepicker') 158 159 def clean(self, value): 160 return datetime.datetime.strptime(value, '%Y-%m-%d').date() 161 162 163class BaseDateBetweenFilter(BaseFilter): 164 """ 165 Base Date Between filter. Consolidates logic for validation and clean. 166 Apply method is different for each back-end. 167 """ 168 def clean(self, value): 169 return [datetime.datetime.strptime(range, '%Y-%m-%d').date() 170 for range in value.split(' to ')] 171 172 def operation(self): 173 return lazy_gettext('between') 174 175 def validate(self, value): 176 try: 177 value = [datetime.datetime.strptime(range, '%Y-%m-%d').date() 178 for range in value.split(' to ')] 179 # if " to " is missing, fail validation 180 # sqlalchemy's .between() will not work if end date is before start date 181 if (len(value) == 2) and (value[0] <= value[1]): 182 return True 183 else: 184 return False 185 except ValueError: 186 return False 187 188 189class BaseDateTimeFilter(BaseFilter): 190 """ 191 Base DateTime filter. Uses client-side date time picker control. 192 """ 193 def __init__(self, name, options=None, data_type=None): 194 super(BaseDateTimeFilter, self).__init__(name, 195 options, 196 data_type='datetimepicker') 197 198 def clean(self, value): 199 # datetime filters will not work in SQLite + SQLAlchemy if value not converted to datetime 200 return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 201 202 203class BaseDateTimeBetweenFilter(BaseFilter): 204 """ 205 Base DateTime Between filter. Consolidates logic for validation and clean. 206 Apply method is different for each back-end. 207 """ 208 def clean(self, value): 209 return [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') 210 for range in value.split(' to ')] 211 212 def operation(self): 213 return lazy_gettext('between') 214 215 def validate(self, value): 216 try: 217 value = [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') 218 for range in value.split(' to ')] 219 if (len(value) == 2) and (value[0] <= value[1]): 220 return True 221 else: 222 return False 223 except ValueError: 224 return False 225 226 227class BaseTimeFilter(BaseFilter): 228 """ 229 Base Time filter. Uses client-side time picker control. 230 """ 231 def __init__(self, name, options=None, data_type=None): 232 super(BaseTimeFilter, self).__init__(name, 233 options, 234 data_type='timepicker') 235 236 def clean(self, value): 237 # time filters will not work in SQLite + SQLAlchemy if value not converted to time 238 timetuple = time.strptime(value, '%H:%M:%S') 239 return datetime.time(timetuple.tm_hour, 240 timetuple.tm_min, 241 timetuple.tm_sec) 242 243 244class BaseTimeBetweenFilter(BaseFilter): 245 """ 246 Base Time Between filter. Consolidates logic for validation and clean. 247 Apply method is different for each back-end. 248 """ 249 def clean(self, value): 250 timetuples = [time.strptime(range, '%H:%M:%S') 251 for range in value.split(' to ')] 252 return [ 253 datetime.time(timetuple.tm_hour, timetuple.tm_min, timetuple.tm_sec) 254 for timetuple in timetuples 255 ] 256 257 def operation(self): 258 return lazy_gettext('between') 259 260 def validate(self, value): 261 try: 262 timetuples = [time.strptime(range, '%H:%M:%S') 263 for range in value.split(' to ')] 264 if (len(timetuples) == 2) and (timetuples[0] <= timetuples[1]): 265 return True 266 else: 267 return False 268 except ValueError: 269 raise 270 return False 271 272 273class BaseUuidFilter(BaseFilter): 274 """ 275 Base uuid filter 276 """ 277 def __init__(self, name, options=None, data_type=None): 278 super(BaseUuidFilter, self).__init__(name, 279 options, 280 data_type='uuid') 281 282 def clean(self, value): 283 value = uuid.UUID(value) 284 return str(value) 285 286 287class BaseUuidListFilter(BaseFilter): 288 """ 289 Base uuid list filter 290 """ 291 292 def clean(self, value): 293 return [str(uuid.UUID(v.strip())) for v in value.split(',') if v.strip()] 294 295 296def convert(*args): 297 """ 298 Decorator for field to filter conversion routine. 299 300 See :mod:`flask_admin.contrib.sqla.filters` for usage example. 301 """ 302 def _inner(func): 303 func._converter_for = list(map(lambda x: x.lower(), args)) 304 return func 305 return _inner 306 307 308class BaseFilterConverter(object): 309 """ 310 Base filter converter. 311 312 Derive from this class to implement custom field to filter conversion 313 logic. 314 """ 315 def __init__(self): 316 self.converters = dict() 317 318 for p in dir(self): 319 attr = getattr(self, p) 320 321 if hasattr(attr, '_converter_for'): 322 for p in attr._converter_for: 323 self.converters[p] = attr 324