django-cruds-adminlte
django-cruds-adminlte copied to clipboard
autocomplete_fields feature?
First, thanks for the package, looking really great! I've tested a few features that worked quite well, but I have a particular problem:
Several of my models refer with a ForeignKey to a geo-database that has ~350,000 entries. I've let django-cruds-adminlte
auto-create all CRUD pages. Whenever I'm trying to load a create/update page of a model that references the foreign key to the geo-database, the connection times out. I have had this problem before with the default django-admin page and it was caused by the model page trying to load all 350,000 entries into the select (see e.g. this StackOverflow). I'm suspecting something similar is happening here.
For the default admin page, this can be fixed by adding the autocomplete_fields
attribute to the respective ModelAdmin
, which tells the ModelAdmin that a particular field is to be searched asynchronously, and the way it is to be searched is defined in the ModelAdmin
of the referenced Model
.
Here's an example for the models:
# models.py
# this model has 350,000 entries
class Geoname(models.Model):
name = models.CharField(max_length=255)
population = models.PositiveIntegerField(blank=True,null=True)
class Institution(models.Model):
name = models.CharField(max_length=200)
city = models.ForeignKey(Geoname,models.SET_NULL,blank=True,null=True)
responsible_for_places = models.ManyToManyField(Geoname,blank=True,null=True,related_name='responsible_institutions')
Using these models to auto-generate CRUDS, the create-page of Institution
times out.
Here's how this is solved in the default admin-framework:
# admin.py
from .models import Geoname, Institution
@admin.register(Geoname)
class GeonameAdmin(admin.ModelAdmin):
search_fields = ['name']
ordering = ['-population']
@admin.register(Institution)
class InstitutionAdmin(admin.ModelAdmin):
autocomplete_fields = ['city', 'responsible_for_places']
Which basically tells django "If a user wants to add a city to an institution, do not load all geoname-values in the select, rather do an Ajax request to the Geoname
-table which is searched in the fields name
and decreasingly order the search results by the field population
. Also, paginate the returned list".
Is there a way to achieve this using django-cruds-adminlte
? I've searched the documentation and the github-issues but could not find anything to achieve this.
Cheers
Edit: Forgot 'population' entry in Model
definition.
I solved this. One has to write a custom django_select2
-Widget, then override the form and override the CRUDview
.
I'm keeping this issue open though as I suspect that either of the following options would help other users:
- Incorporating this solution in the documentation at https://django-cruds-adminlte.readthedocs.io/en/latest/components.html#select2-widget
- automating the process described below such that an additional
autocomplete_fields
-property in theCRUDView
-class can take care of this. I feel as if this should not be too hard but I'm a beginner with django so it's hard for me to judge.
Solution
I've based the following solution on these links:
- Django with Select2 remote data example
- select2 Widget with custom query
- django_select2 Readme
- ModelSelect2 sorting
- ModelSelect2Widget
myproject/urls.py
First, you have to register select2 in your projects/urls.py
as
from django.urls import path, include
urlpatterns = [
...
path('select2/', include('django_select2.urls')),
...
]
because select2 needs its own API to submit searchqueries.
myapp/views.py
The file is split so I can describe what's going on
from django import forms
from cruds_adminlte.crud import CRUDView
from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget
from .models import Institution
First, we import cruds_adminlte
's CRUDview so we can adjust the view. Then, we import ModelSelect2MultipleWidget
, ModelSelect2Widget
which handle Select2-selects
in an asynchronous manner. Subsequently, we import the Model Institution
for which we want the autocomplete-fields.
class SingleSelectWidget(ModelSelect2Widget):
def filter_queryset(self, request, term, queryset, **dependent_fields):
qs = super().filter_queryset(request, term, queryset, **dependent_fields)
return qs.order_by(*self.ordering)
class MultipleSelectWidget(ModelSelect2MultipleWidget):
def filter_queryset(self, request, term, queryset, **dependent_fields):
qs = super().filter_queryset(request, term, queryset, **dependent_fields)
return qs.order_by(*self.ordering)
class SingleGeonameSelectWidget(SingleSelectWidget):
search_fields = ['name__icontains']
ordering = ['-population']
class MultipleGeonameSelectWidget(MultipleSelectWidget):
search_fields = ['name__icontains', 'englishname__icontains']
ordering = ['-population']
The first two classes are helper classes that allow their children to just define the ordering
property for filtering the search query, as one is used to from the admin-pages.
Now define a new form that uses the adjusted widgets:
class InstitutionForm(forms.ModelForm):
class Meta:
model = Institution
exclude = [] # tell django that all fields should be rendered
widgets = {
'city': SingleGeonameSelectWidget,
'responsible_for_places': MultipleGeonameSelectWidget,
}
Use this form to override the CRUDView property:
class InstitutionView(CRUDView):
model = Institution
add_form = InstitutionForm
update_form = InstitutionForm
myapp/urls.py
Register this Model's url
from django.urls import path, include
from django.conf.urls import url
from . import views
urlpatterns = [
url('', include(views.InstitutionView().get_urls())),
]
~~Another problem: the connection does not time out for the add_form
, it still does, however, for the update_form
~~
Edit: I misspelled update_form
in the original solution. I've edited the solution above and it works now.