django jquery autocomplete - как это сделать - фрагмент кода django 233 - обработка внешних ключей с большим количеством значений в формах - PullRequest
0 голосов
/ 10 января 2010

Я нашел хороший фрагмент, который, кажется, является довольно хорошим и универсальным способом автозаполнения ваших виджетов с помощью автозаполнения jquery: ссылка

http://www.djangosnippets.org/snippets/233/

К сожалению, я не нашел полного примера реализации этого фрагмента, и, похоже, я не понимаю его подробно, чтобы иметь возможность реализовать его самостоятельно: D

Поэтому я ищу некоторую помощь, чтобы реализовать это. Некоторые вопросы будут:

_1: когда я назначаю виджет JQueryAutoComplete полю формы. Как указать полю, что оно должно использовать значения из представления, или как указать полю использовать значения из списка.

Должно работать как-то так:

class ProjectForm(forms.ModelForm):

    auto_test = forms.CharField(max_length=10, widget=JQueryAutoComplete())


    class Meta:
        model = Project
        exclude = ('created_by',)

class Project(models.Model):
    name = models.CharField(max_length=100)

    project_owner_externally = models.ForeignKey(Contact, null=True, related_name='project_owner_externally')

_2: В описании фрагмента автор написал: «быть реализованным: - сохранить значение pk в скрытом поле -» - как я могу реализовать скрытое поле в моей форме?

_3: есть ли способ отправить виджет с нужным источником jquery? Или мне нужно установить ссылки jquery на каждой странице, используя этот виджет?

Редактировать: Часть решения:

Хорошо, я сделал несколько шагов дальше к решению.

Мне пришлось внести небольшие изменения во фрагмент:

from django import forms
from django.forms.widgets import flatatt
from django.forms.util import smart_unicode
from django.utils.html import escape
from django.utils.simplejson import JSONEncoder
from django.utils.safestring import mark_safe

class JQueryAutoComplete(forms.TextInput):
    def __init__(self, source, options={}, attrs={}):
        """source can be a list containing the autocomplete values or a
        string containing the url used for the XHR request.

        For available options see the autocomplete sample page::
        http://jquery.bassistance.de/autocomplete/"""

        self.options = None
        self.attrs = {'autocomplete': 'off'}
        self.source = source
        if len(options) > 0:
            self.options = JSONEncoder().encode(options)

        self.attrs.update(attrs)

    def render_js(self, field_id):
        if isinstance(self.source, list):
            source = JSONEncoder().encode(self.source)
        elif isinstance(self.source, str):
            source = "'%s'" % escape(self.source)
        else:
            raise ValueError('source type is not valid')

        options = ''
        if self.options:
            options += ',%s' % self.options

        return u'$(\'#%s\').autocomplete(%s%s);' % (field_id, source, options)

    def render(self, name, value=None, attrs=None):
        final_attrs = self.build_attrs(attrs, name=name)
        if value:
            final_attrs['value'] = escape(smart_unicode(value))

        if not self.attrs.has_key('id'):
            final_attrs['id'] = 'id_%s' % name 

# I added here the mark_safe in order to prevent escaping:
return mark_safe(u'''<input type="text" %(attrs)s/>
        <script type="text/javascript"><!--//
        %(js)s//--></script>
        ''' % {
            'attrs' : flatatt(final_attrs),
            'js' : self.render_js(final_attrs['id']),
        })

С помощью этого фрагмента, помещенного в виджет, вы можете назначить виджет для поля формы следующим образом:

class ProjectForm(forms.ModelForm):

    project_owner_externally = forms.CharField(max_length=25, widget=JQueryAutoComplete('/pm/contact_autocomplete'))

    class Meta:
        model = Project
        exclude = ('created_by',)

    class Media:
            js = (
                settings.MEDIA_URL + "js/jquery.autocomplete.js",
            )
            css = {
                'screen': (settings.MEDIA_URL + "css/jquery.autocomplete.css",),
            }

Обратите внимание на URL-ссылку в экземпляре виджета. widget = JQueryAutoComplete ('/ pm / contact_autocomplete'). Этот URL должен быть определен в вашей конфигурации URL. В моем случае этот URL указывает на представление, которое возвращает отфильтрованные контакты следующим образом:

from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.cache import cache_page
from crm.models import Contact


def contact_autocomplete(request):
    print "holy jquery s..."
    def iter_results(results):
        if results:
            for r in results:
                yield '%s|%s\n' % (r.first_name, r.id)

    if not request.GET.get('q'):
        return HttpResponse(mimetype='text/plain')

    q = request.GET.get('q')
    limit = request.GET.get('limit', 15)
    try:
        limit = int(limit)
    except ValueError:
        return HttpResponseBadRequest() 

    contacts = Contact.objects.filter(first_name__startswith=q)[:limit]
    print contacts
    return HttpResponse(iter_results(contacts), mimetype='text/plain')

И тада .. форма теперь показывает все контакты, найденные в вашем поле ввода.

Это хорошо работает, если вы хотите заполнить поле char некоторыми значениями для поиска в реальном времени.

Вопрос для меня теперь в том, как мне справиться с этим, чтобы не только поместить first_name результата в текстовое поле, но и создать скрытое поле, в котором хранится идентификатор контакта. Для меня остается открытым вопрос, как я могу обработать этот материал для поля ForeignKey, так как project_owner_externally является полем ForeignKey.

project_owner_externally = models.ForeignKey(Contact, null=True, related_name='project_owner_externally')

1 Ответ

2 голосов
/ 11 января 2010

Хорошо, я нашел способ запустить этот фрагмент. Решение довольно хакерское, но оно работает. Было бы здорово, если бы кто-то мог опубликуйте более удобный способ автозаполнения полей внешнего ключа.

Вариант использования этого решения: У нас есть контактная модель с около 2000 контактов. У нас есть проекты, и мы хотим назначить ответственного за проект. Этот человек выбран из 2000 контактов. Поэтому простой Выберите выпадающий не может быть использован. Таким образом, я попытался запустить автозаполнение.

Я пытаюсь объяснить это здесь.

Это две модели:

class Project(models.Model):
    name = models.CharField(max_length=100)
    project_manager_externally = models.ForeignKey(Contact, null=True, related_name='project_manager_externally')

class Contact(models.Model):
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)

Это форма. ProjectForm использует виджет JQueryAutoComplete из http://www.djangosnippets.org/snippets/233/, адаптированный для удовлетворения моих потребностей. Обратите внимание, что поле project_manager_externally является значением модели, которое мы хотим присвоить с помощью ForeignKey для модели Contact. Мы скрываем это поле от пользователя с помощью widget = forms.HiddenInput (). Autocomplete.js обновит это поле для пользователя, когда пользователь выберет запись из автоматически заполненного списка в project_manager_externally_auto_complete. Обратите внимание, что я ограничиваю выбор всеми доступными контактами с помощью choices = contact_choices (). Поскольку JQueryAutoComplete отображается как поле ввода текста, пользователь может вставить все, что он / она хочет. С добавлением опции выбора в поле я оставляю правила проверки вплоть до django.

Последний открытый вопрос был сейчас. Что происходит, когда пользователь вводит правильное имя, но не выбирает его поверх выпадающего списка autocomplete.js. поскольку autocomplete.js обновляет project_manager_externally с правильным контактом pk, контакт pk не будет обновляться, и поэтому мы не будет сохранять новый вход. Поэтому я перезаписываю чистый метод. Сначала я получаю объект Contact с cleaned_data.get ("project_manager_externally"). Если объект Contact был обновлен с помощью autocomplete.js, я могу быть уверен, что first_name и last_name совпадают со значением в project_manager_externally_auto_complete. Как вы видите ниже в виджете JQueryAutoComplete, поле ввода заполняется first_name и last_name. Если они не совпадают, я знаю, что пользователь вставил имя контакта напрямую, без использования выпадающего списка автозаполнения.

from crm.models import Contact
from project_management.models import Project
from ajax_filtered_fields.forms import ForeignKeyByLetter
from django.forms.util import ErrorList

def contact_choices():    
    for contact in Contact.objects.all():
        the_value = contact.first_name + " "  + contact.last_name
        yield (the_value, the_value)

class ProjectForm(forms.ModelForm):

    project_manager_externally_auto_complete = forms.ChoiceField(choices=contact_choices(), label='fake p manager extern', widget=JQueryAutoComplete('/pm/contact_autocomplete'))
    project_manager_externally = forms.ModelChoiceField(queryset=Contact.objects.all(), widget=forms.HiddenInput())

    def clean(self):
        cleaned_data = self.cleaned_data
        project_manager_externally_auto_complete = cleaned_data.get("project_manager_externally_auto_complete")
        project_manager_externally = cleaned_data.get("project_manager_externally")

        if project_manager_externally_auto_complete and project_manager_externally:
            the_value = project_manager_externally.first_name + " "  + project_manager_externally.last_name
            if the_value != project_manager_externally_auto_complete:
                msg = u"Please select a value with the drop-down!"
                self._errors["project_manager_externally_auto_complete"] = ErrorList([msg])

        return cleaned_data

    class Meta:
        model = Project
        exclude = ('created_by',)

   class Media:
            js = (
                settings.MEDIA_URL + "js/jquery.autocomplete.js",
            )
            css = {
                'screen': (settings.MEDIA_URL + "css/jquery.autocomplete.css",),
            }

Адаптированный виджет JQueryAutoComplete: Я просто адаптировал материал возврата js и добавил эту часть. После успешного вызова автозаполнения функция результата заменит значение атрибута на item [1], тогда как item [1] - это pk выбранного контакта. Увидеть смотрите ниже для получения более подробной информации о пункте [1]

from django import forms
from django.forms.widgets import flatatt
from django.forms.util import smart_unicode
from django.utils.html import escape
from django.utils.simplejson import JSONEncoder
from django.utils.safestring import mark_safe

class JQueryAutoComplete(forms.TextInput):
    def __init__(self, source, options={}, attrs={}):
        """source can be a list containing the autocomplete values or a
        string containing the url used for the XHR request.

        For available options see the autocomplete sample page::
        http://jquery.bassistance.de/autocomplete/"""

        self.options = None
        self.attrs = {'autocomplete': 'off'}
        self.source = source
        if len(options) > 0:
            self.options = JSONEncoder().encode(options)

        self.attrs.update(attrs)

    def render_js(self, field_id):
        if isinstance(self.source, list):
            source = JSONEncoder().encode(self.source)
        elif isinstance(self.source, str):
            source = "'%s'" % escape(self.source)
        else:
            raise ValueError('source type is not valid')

        options = ''
        if self.options:
            options += ',%s' % self.options

        #in order to reference to the original field we remove the _auto_complete value from the field_id
        #note that this is a convention. in order to make this work, each form field using this widget has to be named
        #after the field for which it handles the foreign keys + the string '_auto_complete'
        field_id_origin = field_id.replace('_auto_complete', '')

        return u'$(\'#%s\').autocomplete(%s%s).result(function(event, item) {$(\'#%s\').attr("value", item[1]);});' % (field_id, source, options, field_id_origin)  
    def render(self, name, value=None, attrs=None):
        final_attrs = self.build_attrs(attrs, name=name)
        if value:
            final_attrs['value'] = escape(smart_unicode(value))

        if not self.attrs.has_key('id'):
            final_attrs['id'] = 'id_%s' % name    


        return mark_safe(u'''<input type="text" %(attrs)s/>
        <script type="text/javascript"><!--//
        %(js)s//--></script>
        ''' % {
            'attrs' : flatatt(final_attrs),
            'js' : self.render_js(final_attrs['id']),
        })

Вид:

Это также адаптированная часть фрагмента. Важной частью здесь является: yield '% s% s |% s \ n'% (r.first_name, r.last_name, r.id) Единственное, что вы должны понимать здесь, это то, что эта строка является причиной того, что вы можете получить доступ к идентификатору контакта с помощью пункта [1] в ветви результатов вызова автозаполнения выше.

from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.cache import cache_page
from crm.models import Contact
from django.db.models import Q

@cache_page(120)
def contact_autocomplete(request):
    print "holy jquery shit"
    def iter_results(results):
        if results:
            for r in results:
                yield '%s %s|%s\n' % (r.first_name, r.last_name, r.id)

    if not request.GET.get('q'):
        return HttpResponse(mimetype='text/plain')

    q = request.GET.get('q')
    limit = request.GET.get('limit', 15)
    try:
        limit = int(limit)
    except ValueError:
        return HttpResponseBadRequest() 

    contacts = Contact.objects.filter(Q(first_name__istartswith=q) | Q(last_name__istartswith=q))
    return HttpResponse(iter_results(contacts), mimetype='text/plain')

Надеюсь, это поможет кому-нибудь внедрить решение автозаполнения для полей внешнего ключа. Я знаю, что есть менее хакерские решения там, но я не нашел тот, который удовлетворяет мои потребности сегодня. Таким образом, я придумал собственное решение: -)

На самом деле, если у кого-то есть альтернатива для обработки внешних ключей с большим количеством значений, было бы здорово получить сообщение для другого решения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...