18👍
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 B
for 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.site)
)
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):
date_from = forms.DateField(label="From")
date_to = 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
Or simple add {{ form.media }}
to your template as pointed out by software engineer.
6👍
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
- Django – inlineformset_factory with more than one ForeignKey
- Removing the Label From Django's TextArea Widget
- Django + virtualenv + gunicorn – No module named django.core.wsgi?
0👍
I spent a few hours trying to understand why my code would not work, until I stumble on @jenniwren comment about reference to css/js files. Here is a complete working solution.
To use the AutocompleteSelect widget in any custom form for signed-in users having both ‘staff’ and ‘view’ access to the given model, you could declare the following:
from django.urls import reverse
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib import admin
class UserAutocompleteSelect(AutocompleteSelect):
def get_url(self):
model = CustomUser
return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
class UserChoiceField(forms.ModelChoiceField):
def __init__(self, queryset=None, widget=None, **kwargs):
if queryset is None:
queryset = CustomUser.objects.all()
if widget is None:
widget = UserAutocompleteSelect(None, admin.site) # pass `None` for `rel`
super().__init__(queryset, widget=widget, **kwargs)
class UserAutocompleteSelectForm(forms.ModelForm):
"""
for changing user on Play objects
using amdin module autocomplete
"""
user = UserChoiceField(
# queryset=CustomUser.objects.all(),
help_text=_('Select the user to replace the current one')
)
class Meta:
model = Play
fields = ('user', )
You can use the same, replacing CustomUser
and Play
by your own models
And if this is not working out-of-the-box with the html template you’re using, that means that you need to include the required css/js files to your template. Here is a simple way to do it :
Providing that the form is declared as such in the view:
form = UserAutocompleteSelectForm()
...
context = {
'form': form,
...
}
return render(request, 'users/change_user.html', context)
you should add the following lines to the html template to include the required css/js files:
{% block extrahead %}
{{ block.super }}
{{ form.media }}
{% endblock %}
- Django: JSON Notifications using Redis PubSub, Node.js & Socket.io
- Django ManyToMany relation to 'self' without backward relations
- Pycharm remote project with virtualenv
0👍
<link rel="stylesheet" href="{% static 'admin/css/vendor/select2/select2.css' %}" />
<link rel="stylesheet" href="{% static 'admin/css/autocomplete.css' %}"/>
<script src="{% static 'admin/js/vendor/select2/select2.full.js' %}" async></script>
<script src="{% static 'admin/js/autocomplete.js' %}" async></script>
When you have one form
class ExampleForm(forms.Form):
prefix = 'examplePrefix'
Manually use select2, you can use Ajax a data source
<script type="text/javascript">
$(document).ready(function () {
$('#id_{{custom_form.prefix}}-device_types').select2();
});
</script>
In this way if you have multiple form in same view you can use prefixed field name and select2 with or without datasource.
Hence no need to include 3rd party pip or CDN of any kind
- How to get field names when running plain sql query in django
- Difference between self.request and request in Django class-based view
- How to write a Django view for a POST request
- Ignoring case with __startswith