Reputation: 173
I have a Modelformset that raises this validation error on submission:
{'id': ['Select a valid choice. That choice is not one of the available choices.']}
This error appears the same number of times as objects in my queryset, which is:
qs = Task.objects.filter(property=property)
Over the last few days I have been trying to fix this. I've read a lot of other similar posts and tried different solutions but none of them worked for me.
My formset can be seen here:
def add_taskcheck(request, property_pk, pk):
property = get_object_or_404(Property, pk=property_pk)
pcheck = get_object_or_404(Propertycheck, pk=pk)
qs = Task.objects.filter(property=property)
tasks = Task.objects.filter(property=property_pk)
TaskCheckFormset = modelformset_factory(TaskCheck, form=TaskCheckForm, fields=('status','image','notes'), extra=0)
if request.method == 'POST':
formset = TaskCheckFormset(request.POST, request.FILES, queryset=qs)
print(formset.errors)
if formset.is_valid():
taskcheck = formset.save(commit=False)
taskcheck.property_check=pcheck.id
return HttpResponseRedirect(reverse('propertycheck:details', args=[pk]))
else:
formset = TaskCheckFormset(queryset=qs)
context = {
'title':"Add Property Check",
'task':tasks,
'reference':property_pk,
'formset':formset,
}
return render(request, 'propertycheck/add-taskcheck.html', context)
And my form:
class TaskCheckForm(forms.ModelForm):
status = forms.ModelChoiceField(queryset=TaskStatus.objects.all(), to_field_name="name", widget=forms.Select(attrs={
'class':'form-control custom-select',
'id':'type',
}))
image = ...
notes = ...
class Meta:
model = TaskCheck
fields = ('status','image','notes')
And finally my models:
class TaskCheck(models.Model):
status = models.ForeignKey(TaskStatus)
image = models.ImageField(upload_to='task_check', blank=True, null=True)
notes = models.TextField(max_length=500, blank=True)
task = models.ForeignKey(Task)
property_check = models.ForeignKey(Propertycheck)
class Task(models.Model):
task = models.CharField(max_length=100, unique=True)
category = models.ForeignKey(Categories)
property = models.ManyToManyField(Property)
I already know that the problem is not related to 'status' field. Actually I believe that this is related to the 'task' field. I've also added the {{ form.id }}
to template, as I've seen on some other questions.
For reference, my template:
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<div class="form-group">
<h5 class="card-title">{{ form.instance.task }}</h5>
<div class="row">
<div class="col-md-3">
<label for="pname">Status</label>
{{ form.status }}
{{ form.status.errors }}
</div>
<div class="col-md-3">
<label for="pname">Image</label>
{{ form.image }}
{{ form.image.errors }}
</div>
<div class="col-md-3">
<label for="pname">Notes</label>
{{ form.notes }}
{{ form.notes.errors }}
</div>
</div>
</div>
{% endfor %}
So what am I doing wrong?
--------UPDATE------------
From Oleg's answer I changed my formset validation.
if request.method == 'POST':
formset = TaskCheckFormset(request.POST, request.FILES, queryset=qs)
if formset.is_valid():
instances = formset.save(commit=False)
for instance in instances:
# do something with instance
instance.property_check=pcheck.id
instance.save()
Upvotes: 3
Views: 733
Reputation: 434
The issue raised because of different models used in ModelFormSet and QuerySet. The issues can be solved by using:
property = get_object_or_404(Property, pk=property_pk)
pcheck = get_object_or_404(Propertycheck, pk=pk)
qs = Task.objects.filter(property=property)
category = qs.values('category').distinct()
TaskCheckFormset = formset_factory(TaskCheckForm,extra=len(qs))
formset = TaskCheckFormset()
for i in range(len(qs)):
formset.forms[i].initial['task']=qs[i].id
formset.forms[i].instance.task=qs[i]
formset.forms[i].instance.property_check=pcheck
formset.forms[i].initial['property_check']=pcheck.id
Upvotes: 2
Reputation: 1487
Looks like your query is not correct. You are doing
qs = Task.objects.filter(property=property)
It should be like this-
qs = Task.objects.filter(property__id=property.id)
Or you can do like this:
qs = Task.objects.filter(property__in=[property_pk])
Here property
is a many to many field. Your query looks like searching foreign key.
Upvotes: 0
Reputation: 4412
This error points to invalid choice in ModelChoiceField
that in provided example is status field of TaskCheckForm.
This is class level attribute and is initiated only once application starts and TaskCheckForm
is being imported for the first time.
And its QuerySet
is resolved only once at start - and it will see present at that time TaskStatus
objects and never update its choices list for new or deleted items.
To handle relationship fields and other with dynamic queryset recommended way can be used - define empty QuerySet on field and set it to required one in form's __init__
method:
status = forms.ModelChoiceField(queryset=TaskStatus.objects.none(), to_field_name="name", widget=...)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['status'].queryset = TaskStatus.objects.all()
Other potential problem places in code:
tasks = Task.objects.filter(property=property_pk)
- will return a list of results. But later in code is assigned to task
variable in template which may expect (but may be it is ok and it expects list) single item. You can use tasks = Task.objects.filter(property=property_pk).first()
instead.
taskcheck = formset.save(commit=False)
- first, it returns a list of items (because it is formset
to act on a set of forms), so in order to add property_check
attribute to items you need to iterate over result like in example; second - commit=False
means instances will not be saved, which is ok as some additional attribute is set later, but no instance.save()
is called afterwards - so still no changes will be saved.
Upvotes: 1