私のオプションはMultipleInput + Autocompleteです

そもそも、これからの祝日を祝福します!



そして今、私の物語の本質に。



数週間前、djangoでドロップダウンを作成する必要がありました。 入力時に値が自動的にロードされ、ユーザーはリストから値を選択し、独自の値を追加できる必要があります。



まず、私たちが追求している結果を見てみましょう:







そのため、モデルを含むファイル。 たとえば、2つのモデルを作成し、ManyToManyFieldを使用してそれらをリンクしました。



models.py


from django.db import models class City(models.Model): name = models.CharField(max_length=150, unique=True) class Country(models.Model): name = models.CharField(max_length=100) cities = models.ManyToManyField(City, blank=True)
      
      





次に、標準のウィジェットを学ぶために登りました。 MultipleHiddenInputが最適であることが判明しましたが、HiddenInputから継承されており、これまでのところオートコンプリート機能はありませんでした。 「ファイル」>「新規」に進みます。



widget.py


 from django.forms.util import flatatt from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.encoding import force_unicode class MultipleInput(Input): input_type = 'text' def __init__(self, attrs=None, choices=()): super(MultipleInput, self).__init__(attrs) self.choices = choices def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) id_ = final_attrs.get('id', None) inputs = [] for i, v in enumerate(value): input_attrs = dict(value=force_unicode(v), **final_attrs) if id_: input_attrs['id'] = '%s_%s' % (id_, i) inputs.append(u'<p><input%s /><a href="#" id="remove_%s">Remove</a></p>' % (flatatt(input_attrs), id_)) return mark_safe(u'\n'.join(inputs)) def value_from_datadict(self, data, files, name): if isinstance(data, (MultiValueDict, MergeDict)): return data.getlist(name) return data.get(name, None)
      
      





私は何をしましたか? 標準のMultipleHiddenInputを取得し、Inputから継承して、inputs.appendを変更しました。 ご覧のとおり、ほとんど何も変わっていません。 inputs.appendでは、ユーザー側のエントリを削除するために必要なhtmlコード。 なぜこれが必要なのかは、forms.pyファイルについて説明するときに理解できます。



これでフォーム。 citysフィールドには、以前に作成したMultipleInputウィジェットをインストールします。 また、ウィジェットでは、値 'autocompleteCity'を持つ属性 'class'を確認できます。 すでに名前で、これが将来のオートコンプリートに必要であることは明らかです。

ManyToManyFieldバンドルの動作の変更により、__ init__をオーバーライドする必要がフォームに現れました。 ここでは、要素の順序を維持しながら、重複をチェックして削除します。

フォームがエラー付きで送信された場合、この時点で__init__が役に立ち、都市のすべての値を保存してユーザーに送り返します。



forms.py


 from django import forms from myapp.widget import MultipleInput class CreateCountryForm(forms.Form): name = forms.CharField(widget=forms.TextInput(), required=True) cities = forms.CharField(widget=MultipleInput(attrs={'class' : 'autocompleteCity'}), required=False) def __init__(self, *args, **kwargs): super(CreateCountryForm, self).__init__(*args, **kwargs) s = kwargs.get('data', None) if s: cities = s.getlist('cities') for i in xrange(len(cities)-1, -1, -1): if cities.count(cities[i]) != 1: del cities[i]
      
      





モデルを適切に保存するためのマッピングを記述することは残っています。 自動補完のためのajaxリクエストを受け入れるための2番目のマッピング。



views.py


 from django.shortcuts import render_to_response from django.http import HttpResponseRedirect from django.template import RequestContext from myapp.forms import CreateCountryForm from myapp.models import City def create_country(request, form_class=None, template_name='create_country.html'): form_class = CreateCountryForm if request.method == 'POST': form = form_class(data=request.POST, files=request.FILES) if form.is_valid(): obj = form.save(commit=False) obj.save() cities = request.POST.getlist('cities') obj.cities.clear() for c in cities: city, created = City.objects.get_or_create(c) obj.cities.add(city) return HttpResponseRedirect('index') else: form = form_class() context = { 'form': form, } return render_to_response(template_name, context, context_instance=RequestContext(request)) def city_autocomplete(request): try: cities = City.objects.filter(name__icontains=request.GET['q']).values_list('name', flat=True) except MultiValueDictKeyError: pass return HttpResponse('\n'.join(cities), mimetype='text/plain')
      
      





さて、もちろんURL設定。



urls.py


 from django.conf.urls.defaults import * from myapp import views urlpatterns = patterns('' url(r'^city_autocomplete/$', views.city_autocomplete, name='city_autocomplete'), url(r'^create_country/$', views.create_stream, name='stream_create_stream'), )
      
      





最も難しいものをすべて克服し、テンプレートの作成に移りました。 本質を反映するためだけに、継承を一切使用せずに1つのテンプレートを具体的に作成しました。 この例では、オートコンプリートにjQueryAutocompletePluginを使用しています。



create_country.html


 <!DOCTYPE html> <html lang="ru"> <head> <link type="text/css" href="https://github.com/agarzola/jQueryAutocompletePlugin/blob/master/jquery.autocomplete.css" media="all" rel="stylesheet" /> <script type="text/javascript" src="https://github.com/agarzola/jQueryAutocompletePlugin/blob/master/jquery.autocomplete.js"></script> <script type="text/javascript" src="{{ MEDIA_URL }}js/add_and_remove.js"></script> </head> <body> <form enctype="multipart/form-data" action="" method="post">{% csrf_token %} <label for="id_name"></label> {{ form.name }}</br> <a href="#" id="addCity"> </a> <div id="p_cities"> {{ form.cities }} </div> <script type="text/javascript"> jQuery().ready(function() { jQuery(".autocompleteCity").autocomplete("/city_autocomplete/", { multiple: false }); }); </script> <input class="button" type="submit" value=""/> </form> </body> </html>
      
      





完全を期すために、add_and_remove.jsファイルの例を示します。



add_and_remove.js


 $(function() { var CitiesDiv = $('#p_cities'); var i = $('#p_cities p').size(); $('#addCity').live('click', function() { $('<p><input class="autocompleteCity ac_input" type="text" id="id_cities_' + i +'" size="20" name="cities" placeholder="Input Value" autocomplete="off" /><a href="#" id="remove_id_cities">Remove</a></p>').appendTo(CitiesDiv); $('#id_cities_' + i).focus(); i++; jQuery(".autocompleteCity").autocomplete("/city_autocomplete/", { multiple: false }); return false; }); $('#remove_id_cities').live('click', function() { if( i > 0 ) { $(this).parents('p').remove(); i--; } return false; }); });
      
      





PSこれは、私が遭遇した問題に対する私の解決策に過ぎず、最良であると主張するものではありません。 モデル名はランダムに取得されますが、例はサイトにタグを実装するのに適している場合もあります。 コメントと提案に非常に感謝します。djangoとpythonがたった4か月しか好きではないからです。



All Articles