oscarmlage / django-cruds-adminlte

django-cruds is simple drop-in django app that creates CRUD for faster prototyping
BSD 3-Clause "New" or "Revised" License
424 stars 81 forks source link

Select2 not working in inline #134

Open benmaier opened 4 years ago

benmaier commented 4 years ago

I'm using inline CRUD-Views to add more complex information to objects. My setup is similar to the one described #133. I've noticed that custom select2-widgets are not working properly in modals. It's alright to just use the normal selects in some cases, however, for one particular inline-object, I need something to search through 350,000 entries. Unfortunately, when adding a select2-widget to a custom form, the widget does not work: one cannot enter anything and no request is sent to the "select2/"-urls. I've looked at the source code but I can't seem to find any clue on how to proceed. Help would much appreciated.

paging @luisza because they seem to have developed the Inline-logic.

Here's a repository that has the complete example showing what doesn't work: https://github.com/benmaier/example-django-crud-admin-lte-select2

Here's the definitions of models, forms, and views.

models.py

from django.db import models

class Geoname(models.Model):
    name = models.CharField(max_length=255)
    population = models.PositiveIntegerField(blank=True,null=True)

    def __str__(self):
        return self.name

class Institution(models.Model):
    name = models.CharField(max_length=255)
    city = models.ForeignKey(Geoname,models.SET_NULL,blank=True,null=True)
    responsible_for_places = models.ManyToManyField(Geoname,blank=True,related_name='responsible_institutions')

    def __str__(self):
        return self.name

class CooperationalRelationship(models.Model):
    institution = models.ForeignKey(Institution,models.CASCADE,related_name='cooperational_relationships')
    city = models.ForeignKey(Geoname,models.CASCADE,related_name='institutional_relationships')
    description = models.TextField(blank=True,null=True)

    def __str__(self):
        return "{} <-> {}".format(self.institution, self.city)

forms.py

from django import forms

from .models import (
        Institution,
        Geoname,
        CooperationalRelationship,
    )

from .widgets import (
        SingleGeonameSelectWidget,
        MultipleGeonameSelectWidget,
    )
from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget

class SingleGeonameSelectWidget(ModelSelect2Widget):
    search_fields = ['name__icontains']

class MultipleGeonameSelectWidget(ModelSelect2MultipleWidget):
    search_fields = ['name__icontains']

class InstitutionForm(forms.ModelForm):
    class Meta:
        model = Institution
        exclude = []
        widgets = {
                    'city': SingleGeonameSelectWidget,
                    'responsible_for_places': MultipleGeonameSelectWidget,
                }

class CooperationalRelationshipForm(forms.ModelForm):
    class Meta:
        model = CooperationalRelationship
        exclude = ['institution']
        widgets = {
                    'city': SingleGeonameSelectWidget,
                  }

views.py

from django.urls import include
from django.conf.urls import url

from cruds_adminlte.crud import CRUDView
from cruds_adminlte.inline_crud import InlineAjaxCRUD

from .models import (
        Institution,
        Geoname,
        CooperationalRelationship,
    )

from .forms import (
        CooperationalRelationshipForm,
        InstitutionForm,
    )

class CooperationalRelationshipInlineCRUDView(InlineAjaxCRUD):
    model = CooperationalRelationship
    base_model = Institution
    inline_field = 'institution'
    title = "Cooperational Relationships"
    list_fields = ['city', 'description', ]
    add_form = CooperationalRelationshipForm
    update_form = CooperationalRelationshipForm

class InstitutionCRUDView(CRUDView):
    model = Institution
    search_fields = [
            'name__icontains',
            ]
    add_form = InstitutionForm
    update_form = InstitutionForm
    list_fields = ['name','city','responsible_for_places', 'cooperational_relationships']
    inlines = [CooperationalRelationshipInlineCRUDView]

class GeonameCRUDView(CRUDView):
    model = Geoname
    search_fields = [
            'name__icontains',
            ]
    list_fields = ['name', 'population']

def get_urls(basepath='',namespace=None):

    return  [
                url(basepath, include(InstitutionCRUDView().get_urls(), namespace=namespace)),
                url(basepath, include(GeonameCRUDView().get_urls(), namespace=namespace)),
                url(basepath, include(CooperationalRelationshipInlineCRUDView().get_urls(), namespace=namespace)),
            ]

Now, when I navigate to http://localhost:8000/crud/institution/1/update , I see the right form:

Screen Shot 2019-11-01 at 13 32 24

However, when I want to choose a city, I cannot type anything:

Screen Shot 2019-11-01 at 13 32 31

Also, I'm getting these errors in the JavaScript-console:

Screen Shot 2019-11-01 at 13 33 51

Non-custom form

I can disable the custom form:

class CooperationalRelationshipInlineCRUDView(InlineAjaxCRUD):
    model = CooperationalRelationship
    base_model = Institution
    inline_field = 'institution'
    title = "Cooperational Relationships"
    list_fields = ['city', 'description', ]
    #add_form = CooperationalRelationshipForm
    #update_form = CooperationalRelationshipForm

But then, the forms takes ages to load (in this example, it's loading 35,000 items into the select) and also the HTML-selects do not seem to be converted to select2-objects.

Screen Shot 2019-11-01 at 13 36 36

Screen Shot 2019-11-01 at 13 36 46

Related issues

I've seen that a number of people have problems with select2 and modals, so I'm linking a few threads I've found:

benmaier commented 4 years ago

also paging @kptac , you seem to have edited the inline-template because something else did not work in #inline -- maybe you have some advice on how I could proceed to solve this problem? If not, that's completely fine, too.

A4TIF commented 4 years ago

I haven't tested this with your code, but from the console error, you need to serve select2 js from your server instead of cdn as that error seems to be reporting that select2.min.js was blocked because of CORs policy. Also, confirm the jquery version that is required for django-select2. I have not used that before, so not really sure. I think that should resolve it for you (hopefully)

benmaier commented 4 years ago

thank you for your answer! I've since found that the CORs policy errors can be resolved by setting the following variables in your project-settings.py:

SELECT2_JS = 'select2/js/select2.min.js'
SELECT2_CSS = 'select2/css/select2.min.css'
SELECT2_I18N_PATH = 'select2/js/i18n'

I've updated my error-example-repository accordingly.

Unfortunately, this does not resolve the original problem and the select2-select is still unusable. Thanks for your answer though!

benmaier commented 4 years ago

It turns out that this is a long-known problem:

with an easy quick fix:

(remove the tabindex=-1-attribute from the modal)

For my particular case, adding this line to the <script>-section of templates/cruds/ajax/_form.html sufficed:

$(".modal").removeAttr("tabindex");

I'm leaving this issue open nevertheless, until the solution is implemented. Tell me if I should make a pull-request, but it seems like an easy fix. There's probably a better solution, too. I've read that removing tabindex=-1 from a modal removes the ability to close it with the ESC-key.

A4TIF commented 4 years ago

Good find. For the time being, in my opinion, I wouldn't use a modal or the default forms, but a custom form and not load all the entries directly on to the page. Instead, you can easily create an ajax request view, that will return a json response with the entries while users are typing / searching in the select2 js widget. You can read up on that here:

https://select2.org/data-sources/ajax

I have used that in one of my projects before, I can share some of the code for you if you need any assistance with it. Good luck.

benmaier commented 4 years ago

Indeed, I'm already using a custom form with an Ajax request to let users search through the entries :) (see my declarations above in the forms-section).

I just wanted to additionally demonstrate that when one uses the default form, the selects are not converted to select2.

cheers