如何在不提前验证的前提下动态筛选Django ModelForm选项?
Fixing Dynamic Form Filtering and Validation for Your SampleRunSearchForm
I’ve run into this exact issue before—when you dynamically populate form fields based on context from the GET request, you need to make sure that context is also available during POST validation. Here’s how to fix your setup:
Step 1: Refactor the Form to Use ModelMultipleChoiceField
Instead of manually creating a ChoiceField, use ModelMultipleChoiceField—it’s built for selecting multiple model instances and handles queryset filtering/validation seamlessly:
# forms.py from django import forms from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Field, Submit from .models import SampleRun class SampleRunSearchForm(forms.ModelForm): class Meta: model = SampleRun fields = ('id',) def __init__(self, sr_obj=None, *args, **kwargs): super().__init__(*args, **kwargs) # Populate the field with filtered samples if sr_obj is provided if sr_obj is not None: self.fields['id'] = forms.ModelMultipleChoiceField( queryset=sr_obj, required=True, label='Sample:', widget=forms.CheckboxSelectMultiple, ) # Keep your crispy forms setup self.helper = FormHelper() self.helper.layout = Layout( Field('id', css_class='sample-run-display',), Submit('submit', 'Report samples', css_class='upload-btn') ) self.helper.form_method = 'POST'
Step 2: Share Context Between GET and POST in the View
The main problem was that your POST method wasn’t retrieving the filtered sample_run_obj before initializing the form. Let’s refactor the view to reuse the context-fetching logic:
# views.py from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.views import View from .forms import SampleRunSearchForm from .models import Run class SearchSampleRun(View): samplerunform = SampleRunSearchForm template_name = 'results/samplerun_search_form.html' def _get_filtered_samples(self, kwargs): """Reusable method to get context objects for both GET and POST""" self.run_obj = get_object_or_404(Run, id=kwargs['run_id']) self.choice = kwargs['choice'] return self.obtainCorrectSamples() # Your existing filtering method def get(self, request, *args, **kwargs): sample_run_obj = self._get_filtered_samples(kwargs) samplerunform = self.samplerunform(sr_obj=sample_run_obj) return render(request, self.template_name, {'samplerunform': samplerunform}) def post(self, request, *args, **kwargs): sample_run_obj = self._get_filtered_samples(kwargs) # Pass both the filtered queryset AND POST data to the form samplerunform = self.samplerunform(sr_obj=sample_run_obj, data=request.POST) if samplerunform.is_valid(): # Access selected samples with samplerunform.cleaned_data['id'] selected_samples = samplerunform.cleaned_data['id'] # Redirect to your results page return HttpResponseRedirect(f'/results/{self.run_obj.id}/') # If invalid, re-render the form with errors and preserved checkboxes return render(request, self.template_name, {'samplerunform': samplerunform})
Why This Works
- Form Initialization: The form now accepts
sr_objas an optional parameter. When provided, it replaces the defaultidfield with aModelMultipleChoiceFieldusing your filtered queryset. - Shared Context: The
_get_filtered_samplesmethod ensures both GET and POST requests use the same logic to fetch your filtered SampleRun objects. This means the form always has access to the valid choices during validation. - Proper Validation: By passing
sr_objto the form during POST, the form can validate that the submitted IDs are actually part of the filtered set—preventing invalid submissions and keeping the checkbox list intact if validation fails.
Content of the question来源于stack exchange,提问作者trouselife
相关产品推荐
相关产品推荐

