Django: как заставить зависимый выпадающий список загружать отфильтрованный список объектов на форме обновления?
У меня есть несколько зависимых выпадающих списков во всем моем приложении. Чтобы добавить новую запись, форма работает как ожидалось; например, вы выбираете Country
и загружается список State
объектов для этого Country
, затем вы выбираете State
и загружаются Suburb
объекты для этого State
. Вы сохраняете запись, в ней правильно сохранены все поля, просматриваете запись, и вся информация может быть просмотрена, как и ожидалось.
Проблема возникает, когда я хочу изменить/обновить запись. Инстанцированному SuburbForm
приказано сначала загрузить пустой выпадающий список для State
, а затем вызвать функцию load_dropdowns
, когда родительский объект Country
будет установлен. Это действие вызова load_dropdowns
, похоже, не происходит в форме обновления, вместо этого она просто остается пустой, даже если я пытаюсь повторно выбрать родителя.
Если я изменю инстанцированную форму, чтобы она загружала все объекты вместо пустого выпадающего списка, форма обновления заполняет поле правильно, но это означает, что формы добавления нового и обновления теперь загружают все объекты вместо отфильтрованного списка.
models.py
from django.db import models
class Country(models.Model):
item_name = models.CharField(verbose_name="Country")
class State(models.Model):
item_name = models.CharField(verbose_name="State")
state_in_country = models.ForeignKey(Country, on_delete=models.SET_NULL, verbose_name="Country", blank=True, null=True)
class Suburb(models.Model):
item_name = models.CharField(verbose_name="Suburb")
suburb_in_country = models.ForeignKey(Country, on_delete=models.SET_NULL, verbose_name="Country", blank=True, null=True)
suburb_in_state = models.ForeignKey(State, on_delete=models.SET_NULL, verbose_name="State", blank=True, null=True)
forms.py
from django import forms
from .models import Country, State, Suburb
class CountryForm(forms.ModelForm):
class Meta:
model = Country
fields = [
'item_name'
]
class StateForm(forms.ModelForm):
class Meta:
model = State
fields = [
'item_name',
'state_in_country'
]
class SuburbForm(forms.ModelForm):
# separately define fields for dependent dropdowns
suburb_in_country = forms.ModelChoiceField(queryset=Country.objects.filter(deleted=False),widget=forms.Select(attrs={"hx-get": "load_states_for_suburbform/", "hx-target": "#id_suburb_in_state"}),label="Country")
# THE FOLLOWING VARIABLE RESULTS IN A BLANK DROPDOWN LIST WHICH CHANGES TO A FILTERED LIST ON ADD NEW FORM BUT REMAINS BLANK ON UPDATE FORM - SEE __init__ BELOW
suburb_in_state = forms.ModelChoiceField(queryset=State.objects.none(),label="State")
class Meta:
model = Suburb
fields = [
'item_name',
'suburb_in_country',
'suburb_in_state'
]
def __init__(self, *args, **kwargs):
super(SuburbForm, self).__init__(*args, **kwargs)
# handle input from dependent dropdowns
if 'suburb_in_country' in self.data:
suburb_in_country = int(self.data.get("suburb_in_country"))
self.fields['suburb_in_state'].queryset = State.objects.filter(state_in_country=suburb_in_country)
views.py
from django.shortcuts import render,redirect,get_object_or_404
from .forms import *
from .models import *
...
def update(request, id, item):
item = str_to_class(item)
page_title = 'Update ' + item._meta.verbose_name
instance = get_object_or_404(item, id=id)
if request.method == 'POST':
form = str_to_class(item._meta.verbose_name.strip() + 'Form')(request.POST, instance=instance)
if form.is_valid():
form.save()
return redirect(item._meta.list_view)
else:
form = str_to_class(item._meta.verbose_name.strip() + 'Form')(instance=instance)
context = {'form':form,
'title': page_title,
'list_view': item._meta.list_view}
return render(request, 'create.html', context)
...
# Load data for dropdowns that depend on the choice of parent item
# requires model name (item) and the parent field to filter on (parent_field)
def load_dropdowns(request, item, parent_field):
parent_id = 0 if request.GET.get(parent_field) in (None, '') else request.GET.get(parent_field)
if parent_id == 0:
# parent item has not yet been chosen (or blank has been selected)
return render(request, "emptydropdown.html")
if item == State:
# parent item of State is Country
# show only states belonging to the chosen country
items = State.objects.filter(state_in_country_id=parent_id, deleted=False).order_by('item_name')
if item == Suburb:
# parent item of Suburb is State
# show only suburbs belonging to the chosen state
items = Suburb.objects.filter(suburb_in_state_id=parent_id, deleted=False).order_by('item_name')
return render(request, 'dependentdropdown.html', context={'items': items})
...
def load_states_for_suburbform(request):
return load_dropdowns(request, State, 'suburb_in_country')
Решением является дополнительный оператор if в конце SuburbForm
:
class SuburbForm(forms.ModelForm):
# separately define fields for dependent dropdowns
suburb_in_country = forms.ModelChoiceField(queryset=Country.objects.filter(deleted=False),widget=forms.Select(attrs={"hx-get": "load_states_for_suburbform/", "hx-target": "#id_suburb_in_state"}),label="Country")
# THE FOLLOWING VARIABLE RESULTS IN A BLANK DROPDOWN LIST WHICH CHANGES TO A FILTERED LIST ON ADD NEW FORM BUT REMAINS BLANK ON UPDATE FORM - SEE __init__ BELOW
suburb_in_state = forms.ModelChoiceField(queryset=State.objects.none(),label="State")
class Meta:
model = Suburb
fields = [
'item_name',
'suburb_in_country',
'suburb_in_state'
]
def __init__(self, *args, **kwargs):
super(SuburbForm, self).__init__(*args, **kwargs)
# handle input from dependent dropdowns
if 'suburb_in_country' in self.data:
suburb_in_country = int(self.data.get("suburb_in_country"))
self.fields['suburb_in_state'].queryset = State.objects.filter(state_in_country=suburb_in_country)
elif self.instance.pk:
self.fields['suburb_in_state'].queryset = State.objects.filter(state_in_country=self.instance.suburb_in_country)