1b4525b7128aba0feee8d1d1872171553521e1f9
[htsworkflow.git] / htsworkflow / frontend / samples / changelist.py
1 """
2 Slightly modified version of the django admin component that handles filters and searches
3 """
4 from django.contrib.admin.filterspecs import FilterSpec
5 from django.contrib.admin.options import IncorrectLookupParameters
6 from django.core.paginator import Paginator, InvalidPage, EmptyPage
7 from django.db import models
8 from django.db.models.query import QuerySet
9 from django.utils.encoding import smart_str
10 from django.utils.http import urlencode
11
12 import operator
13
14 MAX_SHOW_ALL_ALLOWED = 20000
15
16 #change list settings
17 ALL_VAR = 'all'
18 ORDER_VAR = 'o'
19 ORDER_TYPE_VAR = 'ot'
20 PAGE_VAR = 'p'
21 SEARCH_VAR = 'q'
22 IS_POPUP_VAR = 'pop'
23 ERROR_FLAG = 'e'
24
25 class ChangeList(object):
26     def __init__(self, request, model, list_filter, search_fields, list_per_page, queryset=None):
27         self.model = model
28         self.opts = model._meta
29         self.lookup_opts = self.opts
30         if queryset is None:
31             self.root_query_set = model.objects.all()
32         else:
33             self.root_query_set = queryset
34         self.list_display =  []
35         self.list_display_links = None
36         self.list_filter = list_filter
37
38         self.search_fields = search_fields
39         self.list_select_related = None
40         self.list_per_page = list_per_page
41         self.model_admin = None
42
43         try:
44             self.page_num = int(request.GET.get(PAGE_VAR,'0'))
45         except ValueError:
46             self.page_num = 0
47         self.show_all = 'all' in request.GET
48         self.params = dict(request.GET.items())
49         if PAGE_VAR in self.params:
50             del self.params[PAGE_VAR]
51         if ERROR_FLAG in self.params:
52             del self.params[ERROR_FLAG]
53
54         self.multi_page = True
55         self.can_show_all = False
56       
57         self.order_field, self.order_type = self.get_ordering()
58         self.query = request.GET.get(SEARCH_VAR, '')
59         self.query_set = self.get_query_set()
60         self.get_results(request)
61         self.filter_specs, self.has_filters = self.get_filters(request)
62
63         #self.result_count = 'result count'
64         #self.full_result_count = 'full result count'
65  
66     def get_filters(self, request):
67         filter_specs = []
68         if self.list_filter:
69             filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
70             for f in filter_fields:
71                 spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
72                 if spec and spec.has_output():
73                     filter_specs.append(spec)
74         return filter_specs, bool(filter_specs)
75
76     def get_query_string(self, new_params=None, remove=None):
77         if new_params is None: new_params = {}
78         if remove is None: remove = []
79         p = self.params.copy()
80         for r in remove:
81             for k in p.keys():
82                 if k.startswith(r):
83                     del p[k]
84         for k, v in new_params.items():
85             if v is None:
86                 if k in p:
87                     del p[k]
88             else:
89                 p[k] = v
90         return '?%s' % urlencode(p)
91
92     def get_results(self, request):
93         paginator = Paginator(self.query_set, self.list_per_page)
94         # Get the number of objects, with admin filters applied.
95         result_count = paginator.count
96
97         # Get the total number of objects, with no admin filters applied.
98         # Perform a slight optimization: Check to see whether any filters were
99         # given. If not, use paginator.hits to calculate the number of objects,
100         # because we've already done paginator.hits and the value is cached.
101         if not self.query_set.query.where:
102             full_result_count = result_count
103         else:
104             full_result_count = self.root_query_set.count()
105
106         can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
107         multi_page = result_count > self.list_per_page
108
109         # Get the list of objects to display on this page.
110         if (self.show_all and can_show_all) or not multi_page:
111             result_list = list(self.query_set)
112         else:
113             try:
114                 result_list = paginator.page(self.page_num+1).object_list
115             except InvalidPage:
116                 result_list = ()
117
118         self.result_count = result_count
119         self.full_result_count = full_result_count
120         self.result_list = result_list
121         self.can_show_all = can_show_all
122         self.multi_page = multi_page
123         self.paginator = paginator
124
125     def get_ordering(self):
126         lookup_opts, params = self.lookup_opts, self.params
127         # For ordering, first check the "ordering" parameter in the admin
128         # options, then check the object's default ordering. If neither of
129         # those exist, order descending by ID by default. Finally, look for
130         # manually-specified ordering from the query string.
131         ordering = lookup_opts.ordering or ['-' + lookup_opts.pk.name] 
132
133         if ordering[0].startswith('-'):
134             order_field, order_type = ordering[0][1:], 'desc'
135         else:
136             order_field, order_type = ordering[0], 'asc'
137         if ORDER_VAR in params:
138             try:
139                 field_name = self.list_display[int(params[ORDER_VAR])]
140                 try:
141                     f = lookup_opts.get_field(field_name)
142                 except models.FieldDoesNotExist:
143                     # See whether field_name is a name of a non-field
144                     # that allows sorting.
145                     try:
146                         if callable(field_name):
147                             attr = field_name
148                         elif hasattr(self.model_admin, field_name):
149                             attr = getattr(self.model_admin, field_name)
150                         else:
151                             attr = getattr(self.model, field_name)
152                         order_field = attr.admin_order_field
153                     except AttributeError:
154                         pass
155                 else:
156                     order_field = f.name
157             except (IndexError, ValueError):
158                 pass # Invalid ordering specified. Just use the default.
159         if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
160             order_type = params[ORDER_TYPE_VAR]
161         return order_field, order_type
162
163     def get_query_set(self):
164         qs = self.root_query_set
165         lookup_params = self.params.copy() # a dictionary of the query string
166         for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
167             if i in lookup_params:
168                 del lookup_params[i]
169         for key, value in lookup_params.items():
170             if not isinstance(key, str):
171                 # 'key' will be used as a keyword argument later, so Python
172                 # requires it to be a string.
173                 del lookup_params[key]
174                 lookup_params[smart_str(key)] = value
175
176             # if key ends with __in, split parameter into separate values
177             if key.endswith('__in'):
178                 lookup_params[key] = value.split(',')
179
180         # Apply lookup parameters from the query string.
181         try:
182             qs = qs.filter(**lookup_params)
183         # Naked except! Because we don't have any other way of validating "params".
184         # They might be invalid if the keyword arguments are incorrect, or if the
185         # values are not in the correct type, so we might get FieldError, ValueError,
186         # ValicationError, or ? from a custom field that raises yet something else
187         # when handed impossible data.
188         except Exception, e:
189             print e
190             raise IncorrectLookupParameters
191
192         # Use select_related() if one of the list_display options is a field
193         # with a relationship.
194         if self.list_select_related:
195             qs = qs.select_related()
196         else:
197             for field_name in self.list_display:
198                 try:
199                     f = self.lookup_opts.get_field(field_name)
200                 except models.FieldDoesNotExist:
201                     pass
202                 else:
203                     if isinstance(f.rel, models.ManyToOneRel):
204                         qs = qs.select_related()
205                         break
206
207         # Set ordering.
208         if self.order_field:
209             qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
210
211         # Apply keyword searches.
212         def construct_search(field_name):
213             if field_name.startswith('^'):
214                 return "%s__istartswith" % field_name[1:]
215             elif field_name.startswith('='):
216                 return "%s__iexact" % field_name[1:]
217             elif field_name.startswith('@'):
218                 return "%s__search" % field_name[1:]
219             else:
220                 return "%s__icontains" % field_name
221
222         if self.search_fields and self.query:
223             for bit in self.query.split():
224                 or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields]
225                 other_qs = QuerySet(self.model)
226                 other_qs.dup_select_related(qs)
227                 other_qs = other_qs.filter(reduce(operator.or_, or_queries))
228                 qs = qs & other_qs
229             for field_name in self.search_fields:
230                 if '__' in field_name:
231                     qs = qs.distinct()
232                     break
233
234         if self.opts.one_to_one_field:
235             qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)
236
237         return qs
238
239