|
1 # |
|
2 # Autocomplete feature for admin panel |
|
3 # |
|
4 # Most of the code has been written by Jannis Leidel and was updated a bit |
|
5 # for django_extensions. |
|
6 # http://jannisleidel.com/2008/11/autocomplete-form-widget-foreignkey-model-fields/ |
|
7 # |
|
8 # to_string_function, Satchmo adaptation and some comments added by emes |
|
9 # (Michal Salaban) |
|
10 # |
|
11 import operator |
|
12 from django.http import HttpResponse, HttpResponseNotFound |
|
13 from django.contrib import admin |
|
14 from django.db import models |
|
15 from django.db.models.query import QuerySet |
|
16 from django.utils.encoding import smart_str |
|
17 from django.utils.translation import ugettext as _ |
|
18 from django.utils.text import get_text_list |
|
19 |
|
20 from django_extensions.admin.widgets import ForeignKeySearchInput |
|
21 |
|
22 class ForeignKeyAutocompleteAdmin(admin.ModelAdmin): |
|
23 """Admin class for models using the autocomplete feature. |
|
24 |
|
25 There are two additional fields: |
|
26 - related_search_fields: defines fields of managed model that |
|
27 have to be represented by autocomplete input, together with |
|
28 a list of target model fields that are searched for |
|
29 input string, e.g.: |
|
30 |
|
31 related_search_fields = { |
|
32 'author': ('first_name', 'email'), |
|
33 } |
|
34 |
|
35 - related_string_functions: contains optional functions which |
|
36 take target model instance as only argument and return string |
|
37 representation. By default __unicode__() method of target |
|
38 object is used. |
|
39 """ |
|
40 |
|
41 related_search_fields = {} |
|
42 related_string_functions = {} |
|
43 |
|
44 def __call__(self, request, url): |
|
45 if url is None: |
|
46 pass |
|
47 elif url == 'foreignkey_autocomplete': |
|
48 return self.foreignkey_autocomplete(request) |
|
49 return super(ForeignKeyAutocompleteAdmin, self).__call__(request, url) |
|
50 |
|
51 def foreignkey_autocomplete(self, request): |
|
52 """ |
|
53 Searches in the fields of the given related model and returns the |
|
54 result as a simple string to be used by the jQuery Autocomplete plugin |
|
55 """ |
|
56 query = request.GET.get('q', None) |
|
57 app_label = request.GET.get('app_label', None) |
|
58 model_name = request.GET.get('model_name', None) |
|
59 search_fields = request.GET.get('search_fields', None) |
|
60 object_pk = request.GET.get('object_pk', None) |
|
61 try: |
|
62 to_string_function = self.related_string_functions[model_name] |
|
63 except KeyError: |
|
64 to_string_function = lambda x: x.__unicode__() |
|
65 if search_fields and app_label and model_name and (query or object_pk): |
|
66 def construct_search(field_name): |
|
67 # use different lookup methods depending on the notation |
|
68 if field_name.startswith('^'): |
|
69 return "%s__istartswith" % field_name[1:] |
|
70 elif field_name.startswith('='): |
|
71 return "%s__iexact" % field_name[1:] |
|
72 elif field_name.startswith('@'): |
|
73 return "%s__search" % field_name[1:] |
|
74 else: |
|
75 return "%s__icontains" % field_name |
|
76 model = models.get_model(app_label, model_name) |
|
77 queryset = model._default_manager.all() |
|
78 data = '' |
|
79 if query: |
|
80 for bit in query.split(): |
|
81 or_queries = [models.Q(**{construct_search( |
|
82 smart_str(field_name)): smart_str(bit)}) |
|
83 for field_name in search_fields.split(',')] |
|
84 other_qs = QuerySet(model) |
|
85 other_qs.dup_select_related(queryset) |
|
86 other_qs = other_qs.filter(reduce(operator.or_, or_queries)) |
|
87 queryset = queryset & other_qs |
|
88 data = ''.join([u'%s|%s\n' % ( |
|
89 to_string_function(f), f.pk) for f in queryset]) |
|
90 elif object_pk: |
|
91 try: |
|
92 obj = queryset.get(pk=object_pk) |
|
93 except: |
|
94 pass |
|
95 else: |
|
96 data = to_string_function(obj) |
|
97 return HttpResponse(data) |
|
98 return HttpResponseNotFound() |
|
99 |
|
100 def get_help_text(self, field_name, model_name): |
|
101 searchable_fields = self.related_search_fields.get(field_name, None) |
|
102 if searchable_fields: |
|
103 help_kwargs = { |
|
104 'model_name': model_name, |
|
105 'field_list': get_text_list(searchable_fields, _('and')), |
|
106 } |
|
107 return _('Use the left field to do %(model_name)s lookups in the fields %(field_list)s.') % help_kwargs |
|
108 return '' |
|
109 |
|
110 def formfield_for_dbfield(self, db_field, **kwargs): |
|
111 """ |
|
112 Overrides the default widget for Foreignkey fields if they are |
|
113 specified in the related_search_fields class attribute. |
|
114 """ |
|
115 if (isinstance(db_field, models.ForeignKey) and |
|
116 db_field.name in self.related_search_fields): |
|
117 model_name = db_field.rel.to._meta.object_name |
|
118 help_text = self.get_help_text(db_field.name, model_name) |
|
119 if kwargs.get('help_text'): |
|
120 help_text = u'%s %s' % (kwargs['help_text'], help_text) |
|
121 kwargs['widget'] = ForeignKeySearchInput(db_field.rel, |
|
122 self.related_search_fields[db_field.name]) |
|
123 kwargs['help_text'] = help_text |
|
124 return super(ForeignKeyAutocompleteAdmin, |
|
125 self).formfield_for_dbfield(db_field, **kwargs) |