Ограничение вывода полей в админке Django в соответствии с предыдущим выбором

У меня есть этот model.py:

class Funder(models.Model):
    name = models.CharField(max_length=200)
    scheme = models.ManyToManyField('Scheme', blank=True)
    
class Scheme(models.Model):
    name = models.CharField(max_length=200))

class Project(models.Model):

    title = models.CharField(max_length=200)
    funder = models.ForeignKey(Funder)
    scheme = models.ForeignKey(Scheme, on_delete=models.SET_NULL)

У фонда может быть 0, 1 или много scheme, привязанных к нему. Я хотел бы ограничить выбор схем, которые можно выбрать из административной формы для project, только теми scheme, которые принадлежат конкретному фаундеру. Возможно ли это?

Пример:

в "новом проекте" люди выбирают финансиста1, а в выпадающем списке схем видят только схемы1, схемы3, схемы5, потому что схемы2 и схемы4 связаны с финансистом2.

Можно ли это получить с помощью QuerySet?

Мой ModelAdmin:

from import_export.admin import ImportExportModelAdmin
class FunderAdmin(ImportExportModelAdmin):
    search_fields = ['name',]
    autocomplete_fields = ['scheme']
    list_display = ("name",)

Я могу порекомендовать smart-selects. Я могу сразу предупредить вас в settings.py использовать USE_DJANGO_JQUERY = True вместо: JQUERY_URL = True. Я проверил ваши модели, поле выбора scheme ограничено в зависимости от того, какой funder выбран.

models.py

from smart_selects.db_fields import ChainedForeignKey


class Project(models.Model):
    title = models.CharField(max_length=200)
    funder = models.ForeignKey(Funder, on_delete=models.PROTECT)
    scheme = ChainedForeignKey(
        Scheme,
        chained_field="funder",
        chained_model_field="funder",
        show_all=False,
        auto_choose=True,
        sort=True, null=True)


    def __str__(self):
        return self.title

urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'^chaining/', include('smart_selects.urls')),
]

Как вариант, можно передать данные в javascript function и попытаться отфильтровать их. Но пока мне удалось только зарегистрировать обработку event в виджете формы и получить выбранные funder, но передать данные в javascript я не могу (на всякий случай привожу форму ниже, она не относится к первой рабочей версии):

class ProjectForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = '__all__'

        widgets = {
            'funder': forms.Select(attrs={'onchange': "Load();"})
        }

Для достижения этого с текущей схемой, немного зависит от переопределения Django admin Поэтому я предлагаю решение, в котором вам просто нужно добавить маршрут и функцию выборки и немного переопределить Django admin js ( без каких-либо сторонних зависимостей).

Предположим, что у вас есть такие модели:

class Scheme(models.Model):
    name = models.CharField(max_length=200)

    def __str__(self):
        return f"{self.id}: {self.name}"

class Funder(models.Model):
    name = models.CharField(max_length=200)
    scheme = models.ManyToManyField(Scheme, blank=True)

    def __str__(self):
        return f"{self.id}: {self.name}"

class Project(models.Model):
    title = models.CharField(max_length=200)
    funder = models.ForeignKey(Funder, on_delete=models.CASCADE)
    scheme = models.ForeignKey(Scheme, on_delete=models.SET_NULL, null=True, blank=True)

    def __str__(self):
        return f"{self.id}: {self.title}"

Изменения в проекте admin.py будут выглядеть следующим образом

from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from funder.models import Funder
from .models import Project

from django.urls import path
from django.http import JsonResponse


class ProjectAdmin(ImportExportModelAdmin):
    list_display = ("id", "title", "funder", "scheme")
    list_filter = ("funder",)
    search_fields = ("title",)
    ordering = ("title",)

    class Media:
        # Include JavaScript files in the admin panel
        js = ("js/admin_project.js",)

    def get_urls(self):
        urls = super(ProjectAdmin, self).get_urls()
        custom_urls = [
            path(
                "fetch_schemes/",
                self.fetch_schemes,
                name="project_project_fetch_schemes",
            ),
        ]
        return custom_urls + urls

    def fetch_schemes(self, request):
        funder_id = request.GET.get("funder_id")
        if funder_id:
            try:
                funder = Funder.objects.get(id=funder_id)
                schemes = list(funder.scheme.all().values("id", "name"))
                return JsonResponse({"schemes": schemes})
            except Funder.DoesNotExist:
                return JsonResponse({"error": "Funder not found"})
        else:
            return JsonResponse({"error": "Funder ID not provided"})


admin.site.register(Project, ProjectAdmin)

Теперь просто добавьте js/admin_project.js в вашу папку static в Django

// admin_project.js

jQuery(document).ready(function($) {
    // Store the current value of the scheme dropdown
    var currentSchemeValue = $('#id_scheme').val();

    $('#id_funder').change(function() {
        var funderId = $(this).val();
        
        if (funderId) {
            $.ajax({
                url: "/admin/project/project/fetch_schemes/?funder_id=" + funderId,
                type: 'GET',
                success: function (data) {
                    // Empty the scheme dropdown and add an empty option
                    $('#id_scheme').empty().append('<option value="">---</option>');
                    // Append schemes from the data
                    $.each(data.schemes, function (index, scheme) {
                        $('#id_scheme').append($('<option>', {
                            value: scheme.id,
                            text: `${scheme.id}:${scheme.name}`
                        }));
                    });

                    // Restore the previous value of the scheme dropdown, if it exists
                    if (currentSchemeValue) {
                        $('#id_scheme').val(currentSchemeValue);
                    }
                }
            });
        } else {
            // If no funder is selected, empty the scheme dropdown and add an empty option
            $('#id_scheme').empty().append('<option value="">---</option>');
        }
    });
});

И вы готовы к работе.

Результаты/вывод:

Списки спонсоров Funder lists

Добавить проект без выбора спонсора Add Project scheme field without funder selection

Добавьте поле схемы проекта при выборе спонсора Add project scheme field on funder selection

После сохранения списков проектов (включая пустой сценарий схемы) After saving Project lists (including empty scheme scenario)

Надеюсь, это поможет.

Счастливого кодирования!!!

Вернуться на верх