web/lib/django_extensions/admin/__init__.py
changeset 3 526ebd3988b0
equal deleted inserted replaced
1:ebaad720f88b 3:526ebd3988b0
       
     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)