#
#    Autocomplete feature for admin panel
#
#    Most of the code has been written by Jannis Leidel:
#    http://jannisleidel.com/2008/11/autocomplete-form-widget-foreignkey-model-fields/
#
#    to_string_function, Satchmo adaptation and some comments added by emes (Michal Salaban)
#
from django import forms
from django.db import models
from django.db.models.query import QuerySet
from django.conf import settings
from django.contrib import admin
from django.http import HttpResponse, HttpResponseNotFound
from django.utils.encoding import smart_str
from django.utils.safestring import mark_safe
from django.utils.text import truncate_words
from django.utils.functional import update_wrapper

import operator

class ForeignKeySearchInput(forms.HiddenInput):
    """
    A Widget for displaying ForeignKeys in an autocomplete search input
    instead in a <select> box.
    """

    to_string_function = lambda s: truncate_words(s, 14)

    class Media:
        css = {
            'all': ('css/jquery.autocomplete.css',)
        }
        js = (
            # The js/jquery.js script is referenced in admin/base_site.html template.
            # Requesting it here again would reset all the plugins loaded afterwards.
            'js/jquery.bgiframe.js',
            'js/jquery.ajaxQueue.js',
            'js/jquery.autocomplete.js'
        )

    def label_for_value(self, value):
        key = self.rel.get_related_field().name
        obj = self.rel.to._default_manager.get(**{key: value})
        return self.to_string_function(obj)

    def __init__(self, rel, search_fields, to_string_function, attrs=None):
        self.rel = rel
        self.search_fields = search_fields
        if to_string_function: self.to_string_function = to_string_function
        super(ForeignKeySearchInput, self).__init__(attrs)

    def render(self, name, value, attrs=None):
        if attrs is None:
            attrs = {}
        rendered = super(ForeignKeySearchInput, self).render(name, value, attrs)
        if value:
            label = self.label_for_value(value)
        else:
            label = u''
        return rendered + mark_safe(u'''
            <style type="text/css" media="screen">
                #lookup_%(name)s {
                    padding-right:16px;
                    background: url(
                        %(admin_media_prefix)simg/admin/selector-search.gif
                    ) no-repeat right;
                }
                #del_%(name)s {
                    display: none;
                }
            </style>
<input type="text" id="lookup_%(name)s" value="%(label)s"/>
<a href="#" id="del_%(name)s">
<img src="%(admin_media_prefix)simg/admin/icon_deletelink.gif" />
</a>
<script type="text/javascript">
            var lookup = $('#lookup_%(name)s')
            if (lookup.val()) {
                $('#del_%(name)s').show()
            }
            lookup.attr('size', Math.max(40, lookup.attr('value').length))
            lookup.autocomplete('../search/', {
                formatResult: function(data){ return $('<div />').html(data[0]).text(); },
                extraParams: {
                    search_fields: '%(search_fields)s',
                    app_label: '%(app_label)s',
                    model_name: '%(model_name)s'
                }
            }).result(function(event, data, formatted) {
                if (data) {
                    $('#id_%(name)s').val(data[1]);
                    $('#del_%(name)s').show();
                }
            });
            $('#del_%(name)s').click(function(ele, event) {
                $('#id_%(name)s').val('');
                $('#del_%(name)s').hide();
                $('#lookup_%(name)s').val('');
            });
            </script>
        ''') % {
            'search_fields': ','.join(self.search_fields),
            'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX,
            'model_name': self.rel.to._meta.module_name,
            'app_label': self.rel.to._meta.app_label,
            'label': label,
            'name': name,
        }

class AutocompleteAdmin(admin.ModelAdmin):
    """Admin class for models using the autocomplete feature.

    There are two additional fields:
       - related_search_fields: defines fields of managed model that
         have to be represented by autocomplete input, together with
         a list of target model fields that have to be searched for
         input string,
       - related_string_functions: contains optional functions which
         take target model instance as only argument and return string
         representation. By default __unicode__() method of target
         object is used.
    """

    related_search_fields = {}
    related_string_functions = {}

    def __call__(self, request, url):
        # This is deprecated interface and will be dropped in Django 1.3.
        # Since the version 1.1, Django uses get_urls() method below.
        if url is None:
            pass
        elif url == 'search':
            return self.search(request)
        return super(AutocompleteAdmin, self).__call__(request, url)

    def get_urls(self):
        from django.conf.urls.defaults import url
        
        def wrap(view):
            # This is needed to secure the view so that only admin users can access
            def wrapper(*args, **kwargs):
                return self.admin_site.admin_view(view)(*args, **kwargs)
            return update_wrapper(wrapper, view)

        patterns = super(AutocompleteAdmin, self).get_urls()
        info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
        patterns.insert(
                -1,     # insert just before (.+) rule (see django.contrib.admin.options.ModelAdmin.get_urls)
                url(r'^search/$',
                    wrap(self.search),
                    name='%sadmin_%s_%s_search' % info
                    )
                )
        return patterns

    def search(self, request):
        """
        Searches in the fields of the given related model and returns the
        result as a simple string to be used by the jQuery Autocomplete plugin
        """
        query = request.GET.get('q', None)
        app_label = request.GET.get('app_label', None)
        model_name = request.GET.get('model_name', None)
        search_fields = request.GET.get('search_fields', None)
        try:
            to_string_function = self.related_string_functions[model_name]
        except KeyError:
            to_string_function = lambda x: x.__unicode__()

        if search_fields and app_label and model_name and query:
            def construct_search(field_name):
                # use different lookup methods depending on the notation
                if field_name.startswith('^'):
                    return "%s__istartswith" % field_name[1:]
                elif field_name.startswith('='):
                    return "%s__iexact" % field_name[1:]
                elif field_name.startswith('@'):
                    return "%s__search" % field_name[1:]
                else:
                    return "%s__icontains" % field_name

            model = models.get_model(app_label, model_name)
            qs = model._default_manager.all()
            for bit in query.split():
                or_queries = [models.Q(**{construct_search(
                    smart_str(field_name)): smart_str(bit)})
                        for field_name in search_fields.split(',')]
                other_qs = QuerySet(model)
                other_qs.dup_select_related(qs)
                other_qs = other_qs.filter(reduce(operator.or_, or_queries))
                qs = qs & other_qs
            data = ''.join([u'%s|%s\n' % (to_string_function(f), f.pk) for f in qs])
            return HttpResponse(data)
        return HttpResponseNotFound()

    def formfield_for_dbfield(self, db_field, **kwargs):
        """
        Overrides the default widget for Foreignkey fields if they are
        specified in the related_search_fields class attribute.
        """
        if isinstance(db_field, models.ForeignKey) and \
                db_field.name in self.related_search_fields:
            kwargs['widget'] = ForeignKeySearchInput(
                    db_field.rel,
                    self.related_search_fields[db_field.name],
                    self.related_string_functions.get(db_field.name),
                    )
        field = super(AutocompleteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        return field
