Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

autocomplete_fields feature? #133

Open
benmaier opened this issue Oct 17, 2019 · 2 comments
Open

autocomplete_fields feature? #133

benmaier opened this issue Oct 17, 2019 · 2 comments

Comments

@benmaier
Copy link

benmaier commented Oct 17, 2019

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.

@benmaier
Copy link
Author

benmaier commented Oct 17, 2019

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:

  1. Incorporating this solution in the documentation at https://django-cruds-adminlte.readthedocs.io/en/latest/components.html#select2-widget
  2. automating the process described below such that an additional autocomplete_fields-property in the CRUDView-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:

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())),
        ]

@benmaier
Copy link
Author

benmaier commented Oct 17, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant