📜 ⬆️ ⬇️

My variant MultipleInput + Autocomplete

First of all, I would like to congratulate you on the upcoming holidays!

And now to the essence of my story.

A few weeks ago I needed to make drop-down lists in django. Values ​​should be loaded automatically as you type, and the user should be able to both choose a value from the list and add his own.
')
First, let's see what result we are pursuing:



So, the file with the models. For example, I just created two models and linked them using 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) 

Then I got to learn standard widgets. MultipleHiddenInput turned out to be the most suitable, but it inherits from HiddenInput and so far has not had the autocomplex function. File> New let's go.

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) 

What I've done? I took the standard MultipleHiddenInput, inherited it from Input and changed inputs.append. As you can see, almost nothing has changed. In inputs.append is the html code needed to delete the records on the user side. For what it is necessary you can understand below, when I will describe the forms.py file.

Now form. For the cities field, set the previously written MultipleInput widget. Also, the widget can notice the attribute 'class' with the value 'autocompleteCity'. Already by the name it is clear that this is necessary for the future autocomplex.
In view of the changing behavior of the ManyToManyField bundle, it became necessary to redefine __init__ in the form. Here we check for duplicates and delete them, keeping the order of the elements.
If the form was submitted with errors, it is at this point that __init__ helps us, it saves all the values ​​for cities and sends them back to the user.

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] 

It remains to write a mapping in order to properly save our models. As well as the second mapping to accept ajax requests for autodog.

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') 

Well, of course, the url configuration.

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'), ) 

We overcame all the most difficult, we proceed to writing a template. I specifically wrote one template without various kinds of inheritances, just to display the essence. The example uses jQueryAutocompletePlugin for autocomplete.

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> 

And for completeness, here is an example of the file 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 This is just my solution to the problem I encountered, and he does not claim to be the best. Model names are taken randomly, an example may also be suitable for implementing tags on the site. I would be very grateful for comments and suggestions, for I have only been in love with django and python for 4 months.

Source: https://habr.com/ru/post/135263/


All Articles