Django: Как реализовать фильтрацию выпадающего списка в админке на основе другого?

Проблема: Зависимые выпадающие списки в Django Admin

При работе с Django Admin часто возникает необходимость в реализации зависимых выпадающих списков. Это означает, что выбор опции в одном выпадающем списке должен влиять на доступные опции в другом. Например, при выборе страны в первом выпадающем списке, во втором списке должны отображаться только города, принадлежащие выбранной стране. Стандартная функциональность Django Admin не предоставляет встроенного решения для этой задачи, что требует использования кастомных подходов.

Обзор возможных решений и подходов

Существует несколько способов реализации фильтрации выпадающих списков:

  1. Ручная реализация с использованием JavaScript и AJAX. Этот подход требует написания JavaScript-кода, который будет отправлять AJAX-запросы на сервер для получения отфильтрованных данных и динамического обновления второго выпадающего списка.
  2. Использование сторонних библиотек, таких как django-smart-selects. Эти библиотеки предоставляют готовые инструменты для реализации зависимых выпадающих списков, упрощая процесс разработки и уменьшая объем необходимого кода.
  3. Кастомизация шаблонов Django Admin. Позволяет реализовать более сложную логику фильтрации, требующую значительного изменения стандартного поведения Django Admin.

Необходимость кастомизации административной панели

Кастомизация административной панели Django необходима, когда требуется более сложная логика, чем может предоставить стандартная функциональность. Зависимые выпадающие списки — типичный пример такой необходимости. Кастомизация позволяет улучшить пользовательский опыт и упростить управление данными.

Подготовка моделей Django для фильтрации

Определение связанных моделей с ForeignKey

Для реализации фильтрации необходимо определить модели Django, связанные между собой через ForeignKey. Это позволяет установить иерархические отношения между данными, например, СтранаГородПользователь.

Пример: Модели ‘Страна’, ‘Город’ и ‘Пользователь’

Предположим, у нас есть три модели: Country, City и User.

from django.db import models

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

    def __str__(self) -> str:
        return self.name


class City(models.Model):
    country: models.ForeignKey = models.ForeignKey(Country, on_delete=models.CASCADE)
    name: models.CharField = models.CharField(max_length=100)

    def __str__(self) -> str:
        return self.name


class User(models.Model):
    city: models.ForeignKey = models.ForeignKey(City, on_delete=models.CASCADE)
    username: models.CharField = models.CharField(max_length=100)

    def __str__(self) -> str:
        return self.username

Миграции и заполнение данными для тестирования

После определения моделей необходимо создать миграции и заполнить базу данных тестовыми данными. Это позволит проверить работоспособность фильтрации в Django Admin.

python manage.py makemigrations
python manage.py migrate

Реализация фильтрации с использованием ModelAdmin и JavaScript

Создание кастомного ModelAdmin

Для кастомизации административной панели необходимо создать кастомный ModelAdmin для модели User. Это позволит переопределить стандартное поведение Django Admin.

from django.contrib import admin
from .models import User


class UserAdmin(admin.ModelAdmin):
    list_display: tuple = ('username', 'city')


admin.site.register(User, UserAdmin)

Переопределение формы админки для добавления JavaScript

Для добавления JavaScript-кода необходимо переопределить форму админки и добавить соответствующие скрипты в HTML-код.

from django import forms
from django.contrib import admin
from .models import User, City, Country

class UserAdminForm(forms.ModelForm):
    class Meta:
        model = User
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['city'].queryset = City.objects.none()

        if 'country' in self.data:
            try:
                country_id = int(self.data['country'])
                self.fields['city'].queryset = City.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.city.country.city_set.order_by('name')


class UserAdmin(admin.ModelAdmin):
    form = UserAdminForm
    list_display = ('username', 'city')

    class Media:
        js = ('js/city_filter.js',)

admin.site.register(User, UserAdmin)
Реклама

Написание JavaScript-кода для динамической фильтрации

JavaScript-код должен отправлять AJAX-запросы на сервер для получения отфильтрованных данных и динамического обновления второго выпадающего списка. Пример:

// static/js/city_filter.js

(function($) {
    $(document).ready(function() {
        $("#id_country").change(function() {
            var countryId = $(this).val();
            var citySelect = $("#id_city");

            $.ajax({
                url: '/get_cities/',  // Replace with your actual URL
                data: { country_id: countryId },
                dataType: 'json',
                success: function(data) {
                    citySelect.empty();
                    citySelect.append('<option value="">---------</option>');
                    $.each(data, function(key, value) {
                        citySelect.append('<option value="' + key + '">' + value + '</option>');
                    });
                }
            });
        });
    });
})(django.jQuery);

Использование AJAX-запросов для получения отфильтрованных данных

Необходимо создать представление Django, которое будет обрабатывать AJAX-запросы и возвращать отфильтрованные данные в формате JSON.

from django.http import JsonResponse
from .models import City

def get_cities(request):
    country_id = request.GET.get('country_id')
    cities = City.objects.filter(country_id=country_id).values('id', 'name')
    city_list = {city['id']: city['name'] for city in cities}
    return JsonResponse(city_list)

Реализация фильтрации через django-smart-selects

Установка и настройка django-smart-selects

django-smart-selects — сторонняя библиотека, упрощающая создание связанных выпадающих списков.

Установите пакет:

pip install django-smart-selects

Добавьте 'smart_selects' в INSTALLED_APPS в settings.py.

INSTALLED_APPS = [
    ...
    'smart_selects',
]

Использование ChainedForeignKey и ChainedManyToManyField

Используйте ChainedForeignKey в моделях для создания связанных полей.

from smart_selects.db_fields import ChainedForeignKey

class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class User(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    city = ChainedForeignKey(
        City,
        chained_field="country",
        chained_model_field="country",
        show_all=False,
        auto_choose=True,
        sort=True
    )
    username = models.CharField(max_length=100)

    def __str__(self):
        return self.username

Настройка ModelAdmin для работы с django-smart-selects

Настройка ModelAdmin не требует особых изменений при использовании django-smart-selects.

from django.contrib import admin
from .models import User

class UserAdmin(admin.ModelAdmin):
    list_display = ('username', 'city')

admin.site.register(User, UserAdmin)

Альтернативные подходы и расширенные возможности

Использование сторонних библиотек для расширенной фильтрации

Кроме django-smart-selects, существуют другие библиотеки, предоставляющие расширенные возможности для фильтрации данных в Django Admin. Например, библиотеки для работы с древовидными структурами данных.

Кастомизация шаблонов админки для более сложной логики

Кастомизация шаблонов Django Admin позволяет реализовать более сложную логику фильтрации, требующую значительного изменения стандартного поведения Django Admin. Этот подход требует хорошего знания структуры шаблонов Django Admin.

Обработка ошибок и оптимизация производительности

При реализации фильтрации необходимо учитывать возможные ошибки и оптимизировать производительность, особенно при работе с большими объемами данных. Следует использовать кеширование и оптимизировать запросы к базе данных.

Рекомендации по обеспечению безопасности

При реализации фильтрации необходимо обеспечить безопасность данных, предотвращая SQL-инъекции и другие уязвимости. Следует валидировать входные данные и использовать подготовленные запросы к базе данных.


Добавить комментарий