175
|
1 # -*- coding: utf-8 -*- |
|
2 # |
|
3 # Copyright (C) 2010 Sylvain Beucler |
|
4 # Copyright ??? Django team |
|
5 # |
|
6 # This file is part of Savane. |
|
7 # |
|
8 # Savane is free software: you can redistribute it and/or modify it |
|
9 # under the terms of the GNU Affero General Public License as |
|
10 # published by the Free Software Foundation, either version 3 of the |
|
11 # License, or (at your option) any later version. |
|
12 # |
|
13 # Savane is distributed in the hope that it will be useful, but |
|
14 # WITHOUT ANY WARRANTY; without even the implied warranty of |
|
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
16 # Affero General Public License for more details. |
|
17 # |
|
18 # You should have received a copy of the GNU Affero General Public License |
|
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
20 |
|
21 from django.db import models |
|
22 import operator |
|
23 from django.http import HttpResponse |
|
24 |
|
25 # Copy/paste these: |
|
26 #from django.contrib.admin.views.main import |
|
27 #ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, |
|
28 #SEARCH_VAR, TO_FIELD_VAR, IS_POPUP_VAR, ERROR_FLAG |
|
29 ALL_VAR = 'all' |
|
30 ORDER_VAR = 'o' |
|
31 ORDER_TYPE_VAR = 'ot' |
|
32 PAGE_VAR = 'p' |
|
33 SEARCH_VAR = 'q' |
|
34 TO_FIELD_VAR = 't' |
|
35 IS_POPUP_VAR = 'pop' |
|
36 ERROR_FLAG = 'e' |
|
37 |
|
38 class ChangeList: |
|
39 """ |
|
40 Object to pass views configuration to (e.g.: search string, ordering...) |
|
41 -Draft- |
|
42 """ |
|
43 def __init__(model_admin, request): |
|
44 self.query = request.GET.get(SEARCH_VAR, '') |
|
45 self.list_display = model_admin.list_display |
|
46 |
|
47 def search(f): |
|
48 """ |
|
49 Inspired by Django's admin interface, filter queryset based on GET |
|
50 parameters (contrib.admin.views.main.*_VAR): |
|
51 |
|
52 - o=N: order by ModelAdmin.display_fields[N] |
|
53 - ot=xxx: order type: 'asc' or 'desc' |
|
54 - q=xxx: plain text search on ModelAdmin.search_fields (^ -> istartswith, = -> iexact, @ -> search, each word ANDed) |
|
55 - everything else: name of a Q filter |
|
56 |
|
57 exceptions: |
|
58 - p=N: current page |
|
59 - all=: disable pagination |
|
60 - pop: popup |
|
61 - e: error |
|
62 - to: ? (related to making JS-friendly PK values?) |
|
63 |
|
64 additional exclusions: |
|
65 - page: used by django.views.generic.list_detail |
|
66 |
|
67 We could also try and deduce filters from the Model, or avoid |
|
68 using some declared parameters as Q filters, or find a better |
|
69 idea. |
|
70 """ |
|
71 def _decorator(request, *args, **kwargs): |
|
72 qs = kwargs['queryset'] |
|
73 model_admin = kwargs['model_admin'] |
|
74 |
|
75 lookup_params = request.GET.copy() |
|
76 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, 'page'): |
|
77 if lookup_params.has_key(i): |
|
78 del lookup_params[i] |
|
79 |
|
80 try: |
|
81 qs = qs.filter(**lookup_params) |
|
82 # Naked except! Because we don't have any other way of validating "params". |
|
83 # They might be invalid if the keyword arguments are incorrect, or if the |
|
84 # values are not in the correct type, so we might get FieldError, ValueError, |
|
85 # ValicationError, or ? from a custom field that raises yet something else |
|
86 # when handed impossible data. |
|
87 except: |
|
88 return HttpResponse("Erreur: paramètres de recherche invalides.") |
|
89 #raise IncorrectLookupParameters |
|
90 |
|
91 # TODO: order - but maybe in another, separate filter? |
|
92 |
|
93 ## |
|
94 # Search string |
|
95 ## |
|
96 def construct_search(field_name): |
|
97 if field_name.startswith('^'): |
|
98 return "%s__istartswith" % field_name[1:] |
|
99 elif field_name.startswith('='): |
|
100 return "%s__iexact" % field_name[1:] |
|
101 elif field_name.startswith('@'): |
|
102 return "%s__search" % field_name[1:] |
|
103 else: |
|
104 return "%s__icontains" % field_name |
|
105 |
|
106 query = request.GET.get(SEARCH_VAR, '') |
|
107 search_fields = model_admin.search_fields |
|
108 if search_fields and query: |
|
109 for bit in query.split(): |
|
110 or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in search_fields] |
|
111 qs = qs.filter(reduce(operator.or_, or_queries)) |
|
112 for field_name in search_fields: |
|
113 if '__' in field_name: |
|
114 qs = qs.distinct() |
|
115 break |
|
116 |
|
117 kwargs['queryset'] = qs |
|
118 |
|
119 # TODO: pass order params |
|
120 if not kwargs.has_key('extra_context'): |
|
121 kwargs['extra_context'] = {} |
|
122 kwargs['extra_context']['q'] = query |
|
123 |
|
124 # TODO: move in a clean-up decorator |
|
125 del kwargs['model_admin'] |
|
126 return f(request, *args, **kwargs) |
|
127 return _decorator |