Inital commit
[patchwork] / apps / patchwork / filters.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21 from patchwork.models import Person, State
22 from django.utils.safestring import mark_safe
23 from django.utils.html import escape
24 from django.contrib.auth.models import User
25
26 class Filter(object):
27     def __init__(self, filters):
28         self.filters = filters
29         self.applied = False
30         self.forced = False
31
32     def name(self):
33         """The 'name' of the filter, to be displayed in the filter UI"""
34         return self.name
35
36     def condition(self):
37         """The current condition of the filter, to be displayed in the
38            filter UI"""
39         return self.key
40
41     def key(self):
42         """The key for this filter, to appear in the querystring. A key of
43            None will remove the param=ley pair from the querystring."""
44         return None
45
46     def set_status(self, *kwargs):
47         """Views can call this to force a specific filter status. For example,
48            a user's todo page needs to setup the delegate filter to show
49            that user's delegated patches"""
50         pass
51
52     def parse(self, dict):
53         if self.param not in dict.keys():
54             return
55         self._set_key(dict[self.param])
56
57     def url_without_me(self):
58         return self.filters.querystring_without_filter(self)
59
60     def form_function(self):
61         return 'function(form) { return "unimplemented" }'
62
63     def form(self):
64         if self.forced:
65             return mark_safe('<input type="hidden" value="%s">%s' % (self.param,
66                         self.condition()))
67             return self.condition()
68         return self._form()
69
70     def kwargs(self):
71         return {}
72
73     def __str__(self):
74         return '%s: %s' % (self.name, self.kwargs())
75
76
77 class SubmitterFilter(Filter):
78     param = 'submitter'
79     def __init__(self, filters):
80         super(SubmitterFilter, self).__init__(filters)
81         self.name = 'Submitter'
82         self.person = None
83         self.person_match = None
84
85     def _set_key(self, str):
86         self.person = None
87         self.person_match = None
88         submitter_id = None
89         try:
90             submitter_id = int(str)
91         except ValueError:
92             pass
93         except:
94             return
95
96         if submitter_id:
97             self.person = Person.objects.get(id = int(str))
98             self.applied = True
99             return
100
101
102         people = Person.objects.filter(name__icontains = str)
103
104         if not people:
105             return
106
107         self.person_match = str
108         self.applied = True
109
110     def kwargs(self):
111         if self.person:
112             user = self.person.user
113             if user:
114                 return {'submitter__in':
115                     Person.objects.filter(user = user).values('pk').query}
116             return {'submitter': self.person}
117
118         if self.person_match:
119             return {'submitter__name__icontains': self.person_match}
120         return {}
121
122     def condition(self):
123         if self.person:
124             return self.person.name
125         elif self.person_match:
126             return self.person_match
127         return ''
128
129     def _form(self):
130         name = ''
131         if self.person:
132             name = self.person.name
133         return mark_safe(('<input onKeyUp="submitter_field_change(this)" ' +
134                         'name="submitter" id="submitter_input" ' +
135                         'value="%s">&nbsp;' % escape(name)) +
136                         '<select id="submitter_select" ' +
137                         'disabled="true"></select>')
138
139     def key(self):
140         if self.person:
141             return self.person.id
142         return self.person_match
143
144 class StateFilter(Filter):
145     param = 'state'
146     def __init__(self, filters):
147         super(StateFilter, self).__init__(filters)
148         self.name = 'State'
149         self.state = None
150
151     def _set_key(self, str):
152         try:
153             self.state = State.objects.get(id=int(str))
154         except:
155             return
156
157         self.applied = True
158
159     def kwargs(self):
160         return {'state': self.state}
161
162     def condition(self):
163         return self.state.name
164
165     def key(self):
166         if self.state is None:
167             return None
168         return self.state.id
169
170     def _form(self):
171         str = '<select name="%s">' % self.param
172         str += '<option value="">any</option>'
173         for state in State.objects.all():
174             selected = ''
175             if self.state and self.state == state:
176                 selected = ' selected="true"'
177
178             str += '<option value="%d" %s>%s</option>' % \
179                 (state.id, selected, state.name)
180         str += '</select>'
181         return mark_safe(str);
182
183     def form_function(self):
184         return 'function(form) { return form.x.value }'
185
186 class SearchFilter(Filter):
187     param = 'q'
188     def __init__(self, filters):
189         super(SearchFilter, self).__init__(filters)
190         self.name = 'Search'
191         self.param = 'q'
192         self.search = None
193
194     def _set_key(self, str):
195         str = str.strip()
196         if str == '':
197             return
198         self.search = str
199         self.applied = True
200
201     def kwargs(self):
202         return {'name__icontains': self.search}
203
204     def condition(self):
205         return self.search
206
207     def key(self):
208         return self.search
209
210     def _form(self):
211         value = ''
212         if self.search:
213             value = escape(self.search)
214         return mark_safe('<input name="%s" value="%s">' %\
215                         (self.param, value))
216
217     def form_function(self):
218         return mark_safe('function(form) { return form.x.value }')
219
220 class ArchiveFilter(Filter):
221     param = 'archive'
222     def __init__(self, filters):
223         super(ArchiveFilter, self).__init__(filters)
224         self.name = 'Archived'
225         self.archive_state = False
226         self.applied = True
227         self.param_map = {
228             True: 'true',
229             False: '',
230             None:  'both'
231         }
232         self.description_map = {
233             True: 'Yes',
234             False: 'No',
235             None: 'Both'
236         }
237
238     def _set_key(self, str):
239         self.archive_state = False
240         self.applied = True
241         for (k, v) in self.param_map.iteritems():
242             if str == v:
243                 self.archive_state = k
244         if self.archive_state == None:
245             self.applied = False
246
247     def kwargs(self):
248         if self.archive_state == None:
249             return {}
250         return {'archived': self.archive_state}
251
252     def condition(self):
253         return self.description_map[self.archive_state]
254
255     def key(self):
256         if self.archive_state == False:
257             return None
258         return self.param_map[self.archive_state]
259
260     def _form(self):
261         s = ''
262         for b in [False, True, None]:
263             label = self.description_map[b]
264             selected = ''
265             if self.archive_state == b:
266                 selected = 'checked="true"'
267             s += ('<input type="radio" name="%(param)s" ' + \
268                    '%(selected)s value="%(value)s">%(label)s' + \
269                    '&nbsp;&nbsp;&nbsp;&nbsp;') % \
270                     {'label': label,
271                      'param': self.param,
272                      'selected': selected,
273                      'value': self.param_map[b]
274                     }
275         return mark_safe(s)
276
277     def url_without_me(self):
278         qs = self.filters.querystring_without_filter(self)
279         if qs != '?':
280             qs += '&'
281         return qs + 'archive=both'
282
283
284 class DelegateFilter(Filter):
285     param = 'delegate'
286     AnyDelegate = 1
287
288     def __init__(self, filters):
289         super(DelegateFilter, self).__init__(filters)
290         self.name = 'Delegate'
291         self.param = 'delegate'
292
293         # default to applied, but no delegate - this will result in patches with
294         # no delegate
295         self.delegate = None
296         self.applied = True
297
298     def _set_key(self, str):
299         if str == "*":
300             self.applied = False
301             self.delegate = None
302             return
303
304         applied = False
305         try:
306             self.delegate = User.objects.get(id = str)
307             self.applied = True
308         except:
309             pass
310
311     def kwargs(self):
312         if not self.applied:
313             return {}
314         return {'delegate': self.delegate}
315
316     def condition(self):
317         if self.delegate:
318             return self.delegate.get_profile().name()
319         return 'Nobody'
320
321     def _form(self):
322         delegates = User.objects.filter(userprofile__maintainer_projects =
323                 self.filters.project)
324
325         str = '<select name="delegate">'
326
327         selected = ''
328         if not self.applied:
329             selected = 'selected'
330
331         str += '<option %s value="*">------</option>' % selected
332
333         selected = ''
334         if self.delegate is None:
335             selected = 'selected'
336
337         str += '<option %s value="">Nobody</option>' % selected
338
339         for d in delegates:
340             selected = ''
341             if d == self.delegate:
342                 selected = ' selected'
343
344             str += '<option %s value="%s">%s</option>' % (selected,
345                     d.id, d.get_profile().name())
346         str += '</select>'
347
348         return mark_safe(str)
349
350     def key(self):
351         if self.delegate:
352             return self.delegate.id
353         if self.applied:
354             return None
355         return '*'
356
357     def url_without_me(self):
358         qs = self.filters.querystring_without_filter(self)
359         if qs != '?':
360             qs += '&'
361         return qs + ('%s=*' % self.param)
362
363     def set_status(self, *args, **kwargs):
364         if 'delegate' in kwargs:
365             self.applied = self.forced = True
366             self.delegate = kwargs['delegate']
367         if self.AnyDelegate in args:
368             self.applied = False
369             self.forced = True
370
371 filterclasses = [SubmitterFilter, \
372                  StateFilter,
373                  SearchFilter,
374                  ArchiveFilter,
375                  DelegateFilter]
376
377 class Filters:
378
379     def __init__(self, request):
380         self._filters = map(lambda c: c(self), filterclasses)
381         self.dict = request.GET
382         self.project = None
383
384         for f in self._filters:
385             f.parse(self.dict)
386
387     def set_project(self, project):
388         self.project = project
389
390     def filter_conditions(self):
391         kwargs = {}
392         for f in self._filters:
393             if f.applied:
394                 kwargs.update(f.kwargs())
395         return kwargs
396
397     def apply(self, queryset):
398         kwargs = self.filter_conditions()
399         if not kwargs:
400             return queryset
401         return queryset.filter(**kwargs)
402
403     def params(self):
404         return [ (f.param, f.key()) for f in self._filters \
405                 if f.key() is not None ]
406
407     def querystring(self, remove = None):
408         params = dict(self.params())
409
410         for (k, v) in self.dict.iteritems():
411             if k not in params:
412                 params[k] = v[0]
413
414         if remove is not None:
415             if remove.param in params.keys():
416                 del params[remove.param]
417
418         return '?' + '&'.join(['%s=%s' % x for x in params.iteritems()])
419
420     def querystring_without_filter(self, filter):
421         return self.querystring(filter)
422
423     def applied_filters(self):
424         return filter(lambda x: x.applied, self._filters)
425
426     def available_filters(self):
427         return self._filters
428
429     def set_status(self, filterclass, *args, **kwargs):
430         for f in self._filters:
431             if isinstance(f, filterclass):
432                 f.set_status(*args, **kwargs)
433                 return