I have a simple foreign key relationship I want to use in a ModelForm, but without a ModelChoiceField.
class Sample(models.Model):
alt = IntegerField(db_index=True, unique=True)
class Assignment(models.Model):
sample = models.ForeignKey(Sample, on_delete=models.CASCADE)
I want to have the AssignmentForm select the sample based on the contents of the sample's alt field. With a ModelChoiceField it would be like this:
class SampleSelect(ModelChoiceField):
def label_from_instance(self, obj):
return obj.alt
class AssignmentForm(ModelForm):
sample = SampleSelect(queryset=Sample.objects.all())
class Meta:
model = Assignment
fields = ['sample']
The ModelChoiceField documentation says to use something else if the number of choices is large.
Allows the selection of a single model object, suitable for representing a foreign key. Note that the default widget for ModelChoiceField becomes impractical when the number of entries increases. You should avoid using it for more than 100 items.
I think I need a custom form field, but I cannot figure out how to do this.
class SampleBAltField(IntegerField):
def clean(self, value):
try:
return Sample.objects.get(alt=value)
except Sample.DoesNotExist:
raise ValidationError(f'Sample with alt {value} does not exist')
This existing code should take an integer from the form and map it back to a foreign key, but I cannot figure out what to override to populate the field for a bound form from the Sample instance.
Is there a relatively easy way to solve this issue with FormFields in the ModelForm, or do I need to write the Form from scratch?
The ModelChoiceField documentation says to use something else if the number of choices is large.
The documentation suggests using a different widget (otherwise a user will have to select from a dropdown with too many items), but you don't necessarily need an entirely different field.
If you want the field of the bound form to be n instance of Sample
, then ModelChoiceField
is still appropriate.
To avoid the problem anticipated in the documentation, you could just change the widget for the field. You might need to decide exactly what that is. One simple choice would be to use a NumberInput
widget where the user just enters an integer for the foreign key.
from django.forms.widgets import NumberInput
class AssignmentForm(ModelForm):
sample = ModelChoiceField(queryset=Sample.objects.all(), widget=NumberInput)
class Meta:
model = Assignment
fields = ['sample']
select the sample based on the contents of the sample's alt field
What you want here is a separate issue from what you quoted from the documentation. You can choose to implement this with or without changing the widget.
If you want the user to provide the alt
value rather than the primary key of the Sample, you can use the to_field_name
argument for ModelChoiceField
(note this is only appropriate here because your alt
field is unique)
class AssignmentForm(ModelForm):
sample = ModelChoiceField(
queryset=Sample.objects.all(),
widget=NumberInput,
help_text="Enter the alt of the sample",
to_field_name='alt'
)
class Meta:
model = Assignment
fields = ["sample"]
In order for the initial value to render correctly when rendering a bound form, you can provide the initial
keyword argument when instantiating the bound form:
form = AssignmentForm(instance=inst,
initial={'sample': inst.sample.alt})
Alternatively, you can override the __init__
method of the form to do this automatically when the form is instantiated:
class AssignmentForm(ModelForm):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.initial.update({'sample': self.instance.sample.alt})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With