Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django, how to use the admin autocomplete field in a custom form

Tags:

django

In the Django admin interface you can set a field to be a autocomplete field, e.g.:

autocomplete_fields = ('countries', )

This works great for admin pages, how does one use the autocomplete field in a custom view/form? My research points towards django-autocomplete-light, but it seems non-ideal to install a 3rd party package when Django already has the functionality built-in.

like image 446
run_the_race Avatar asked Jul 22 '19 09:07

run_the_race


2 Answers

Indeed it is possible to use the select2 from the admin. You simply need to use the AutocompleteSelect widget. The AutocompleteSelect widget expects an relation and an admin site.

If you have model A that has a ForeignKey field pointing to model Bfor which you want to use the AutocompleteSelect, you simply can use (as suggested by cuto).

from django.contrib.admin.widgets import AutocompleteSelect
from myapp.model import ModelA, ModelB
from django.contrib import admin


class MyForm(form.Form):
    model_b = forms.ModelChoiceField(
        queryset=ModelB.objects.all(),
        widget=AutocompleteSelect(ModelA._meta.get_field('model_b').remote_field, admin.AdminSite)
    )

As I did not have a relation I used a FakeRelation class, as only the model property is used by the get_url(self) function. The usage of the AutocompleteSelect widget is bound to the same condition as the the usage of the autocomplete_fields widget.

from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib import admin
from django import forms
from myapp.models import countries

class FakeRelation:
    def __init__(self, model):
        self.model = model


class CustomAutocompleteSelect (AutocompleteSelect):
    def __init__(self, model, admin_site, attrs=None, choices=(), using=None):
        rel = FakeRelation(model)
        super().__init__(rel, admin_site, attrs=attrs, choices=choices, using=using)

class PreventionPlanForm(form.Form):
    DateFrom = forms.DateField(label="From")
    DateTo = forms.DateField(label="To")
    PE1_Name = forms.ModelChoiceField(
        queryset=countries.objects.all(),
        widget=CustomAutocompleteSelect(countries, admin.AdminSite)
    )

As jenniwren pointed out: Make sure to load the correct Javascripts/CSS files in your template (path could be subject to change in different django versions):

  • admin/css/vendor/select2/select2.css
  • admin/js/vendor/select2/select2.full.js
  • admin/css/autocomplete.css
  • admin/js/autocomplete.js
like image 76
Kound Avatar answered Oct 29 '22 08:10

Kound


The FakeRelation solution proposed before does not work anymore with Django 2.2 which requires a field instance for the AutocompleteSelect constructor.
I had a suitable field in my project, so I could use it, but had to pass a field instance instead of a relation. Here is the code for a custom AutocompleteSelect which also add the option to pass a specific placeholder to Select2:

class CustomAutocompleteSelect(AutocompleteSelect):
    def __init__(self, field, prompt="", admin_site=None, attrs=None, choices=(), using=None):
        self.prompt = prompt
        super().__init__(field, admin_site, attrs=attrs, choices=choices, using=using)

    def build_attrs(self, base_attrs, extra_attrs=None):
        attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
        attrs.update({
            'data-ajax--delay': 250,
            'data-placeholder': self.prompt,
            'style': 'width: 30em;'
        })
        return attrs


class AddLittermateForm(forms.Form):
    new_littermate = forms.ModelChoiceField(
        queryset=Dog.objects.all(),
        widget=CustomAutocompleteSelect(LitterDog._meta.get_field(
            'dog'), "Search for a littermate here", admin.site)
    )

If one did not have a model with a suitable relation, they would have to declare one that is not managed:

...
class Meta:
    managed = False
like image 33
jphilip Avatar answered Oct 29 '22 07:10

jphilip