'How to use chained django_select2 selects in django admin panel?

I took almost unchanged guide from documentation there is my models.py

from django.db import models


class Country(models.Model):
    name = models.CharField(max_length=255)


class City(models.Model):
    name = models.CharField(max_length=255)
    country = models.ForeignKey('Country', related_name="cities", on_delete=models.CASCADE)


class Address(models.Model):
    country = models.ForeignKey('Country', on_delete=models.CASCADE)
    city = models.ForeignKey('City', on_delete=models.CASCADE)

and have created ModelForm because simple Form from documentation guide is not fits for admin panel

class AddressForm(forms.ModelForm):
    class Meta:
        model = Address
        fields = '__all__'
        widgets = {
            'country': ModelSelect2Widget(
                model=Country,
                search_fields=['name__icontains'],
            ),
            'city': ModelSelect2Widget(
                model=City,
                search_fields=['name__icontains'],
                dependent_fields={'country': 'country'},
                max_results=500,
            )
        }

on the simple, not admin, page it works fine. I tried it just for testing. I really need only in admin panel. here is the template from docs

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Create Book</title>
    {{ form.media.css }}
    <style>
        input, select {width: 100%}
    </style>
</head>
<body>
    <h1>Create a new Book</h1>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit">
    </form>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    {{ form.media.js }}
</body>
</html>

and view class

class AddressCreateView(generic.CreateView):
    model = models.Address
    form_class = forms.AddressForm
    success_url = "/"
    template_name = 'address.html'

But in the admin panel the same form is not works. Selects does not containing any items and there is no search field shown there is also no requests sent to the server with I can see when interact with selects at the simple page

from django.contrib import admin
from .models import City, Country, Address
from .forms import AddressForm


@admin.register(Address)
class AddressAdmin(admin.ModelAdmin):
    class Media:
        js = (
            'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js',
        )

    form = AddressForm


admin.site.register(City)
admin.site.register(Country)


Solution 1:[1]

This answer will save you a lot of time, DON'T use 'django_select2' to implement chained select fields in Django admin, instead use Django AutoComplete Light with Django-Hacker. where the DAL will be responsible to implement AJAX requests to retrieve the data for the chained Select2 field while Django-Hacker will save your time by customize default Django forms directly from anywhere in the api without need to create specific form to your admin class.

STEP1: pip the mentioned packages and then add below packages at top of INSTALLED_APPS in settings.py file:

settings.py

INSTALLED_APPS = [
'dal',
'dal_select2',
]

Note: this will make sure to override the jquery.init.js script provided by the admin, which sets up jQuery with noConflict, making jQuery available in django.jQuery only and not $ (global).

STEP2: Make sure you have registered your api urls.py file in the main urls.py file of your Django project.

STEP3: Create the view class that will be used to retrieve and update the data (using AJAX) for your chained Select2 field on the admin page, as shown in the below example.

views.py

from dal import autocomplete

from your_api.models import model_name, depend_on_model


class MyAutocompleteView(autocomplete.Select2QuerySetView):
    """Select2 autocomplete view class to return queryset for chained Select2 field
    on admin page depending on specific field value"""

    def get_queryset(self):
        """Customize the queryset for this class view"""

        # Note: Don't forget to filter out results depending on the visitor !

        # Make sure the HTTP request is made by authenticated user, if 
        # unauthenticated then return nothing.
        if not self.request.user.is_authenticated:
            return model_name.objects.none()

        value = self.forwarded.get('query_string_name', None)

        # Check if HTTP request have a value for the query string (URL parameter).
        if value:
            queryset = depend_on_model.objects.filter(
                depend_on_field=value
            ).order_by('-created_at')
    
            return queryset
    

STEP4: Finally you need to register this view inside your api urls.py file and at the same time create a form field for your chained Select2 field.

urls.py


from django.urls import path
from django import forms

from dal import autocomplete

import djhacker

from your_api import views
from your_api.models import model_name

# Define a variable that help to identify which api that creating URL from when
# using reverse function.
app_name = 'your_api'

urlpatterns = [
    path(
        'my-autocomplete/',
        views.MyAutocompleteView.as_view(),
        name='my-autocomplete',
    ),
]

# Now hack your model field to always render the autocomplete field with
# that view!
djhacker.formfield(
    # Specify the field of the model.
    model_name.field_name_to_be_chained_selec2,
    # Specify the form field type, since our field is a Foreign key, so we set
    # it to be choice field.
    forms.ModelChoiceField,
    # Set the widget for this form field.
    widget=autocomplete.ModelSelect2(
        # Set your django api url:
        # '<api name>:<name of the registered view path>'
        #
        # Note: make sure you registered the api routes in your main 'urls.py' file.
        url='your_api:my-autocomplete',
        # Set the depend on field in order to be forward its value to the view.
        forward=['depend_on_field'],
        # You can set some options for this Select2 field.
        attrs={
            # Set placeholder
            'data-placeholder': 'Autocomplete...',
            },
        )
)

That's all, we just made a chained Select2 field that depend on another field value and you can check it on Django admin page for the related model without need to make complex customize ajax file, customize your Django admin JavaScript or add cache server.. etc.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Wadhah Sky