Django foreign key as options for autocomplete, error: Select a valid choice
Thanks in advance for your help. I am building my first Django project, a ticketing application that will have over 100 items to choose from.
I'm trying to display a list of Items(foreign key objects) for the user to choose from when creating a ticket in an MDBootstrap autocomplete field. The items have both a name and ID and I want both to appear in the autocomplete options when the user is searching.
My form works when I display only the 'item_id' from the Item model in the autocomplete data (found in the script tags at the bottom of the template). But when I display the string with both 'item_name" and 'item_id'(as you see below), my form won't submit and I get the error "Select a valid choice. That choice is not one of the available choices."
How can I display both 'item_name' and 'item_id' from the Item model in the autocomplete options but then have my form submit properly recognizing the 'item_id' for its 'item' foreign key field?
models (each of these models is in a different app within the django project)
class Item(models.Model):
item_name = models.CharField(max_length=200)
item_id = models.CharField(max_length=200, primary_key=True)
class Ticket(models.Model):
ticket_id = models.AutoField(primary_key=True, null=False, blank=False)
item = models.ForeignKey(Item, on_delete=models.PROTECT, null=False, blank=False, related_name="tickets")
view
def TicketSubmit(request):
items = Item.objects.all()
if request.method == "POST":
item = request.POST.get("item")
form = TicketSubmitForm(request.POST)
photoform = TicketImageForm(request.POST or None)
files = request.FILES.getlist("ticket_photo")
if form.is_valid():
f = form.save(commit=False)
f.item = item
for i in files:
TicketPhoto.objects.create(ticket=f, ticket_photo=i)
messages.success(request, "Success! New Ticket Created")
return HttpResponseRedirect(reverse_lazy("home"))
else:
print(form.errors)
else:
form = TicketSubmitForm()
photoform = TicketImageForm()
context = {"form": form, "photoform": photoform, "items": list(items)}
return render(request, "home.html", context)
form
class TicketSubmitForm(forms.ModelForm):
class Meta:
model = Ticket
fields = ["item", "op_name", "ticket_text"]
widgets = {
"created_at": forms.HiddenInput,
"completed": forms.HiddenInput,
"item": forms.TextInput(
attrs={
"class": "form-control",
"id": "form11",
}
),
"op_name": forms.TextInput(
attrs={
"class": "form-control",
"id": "op_nameInput",
"placeholder": "Enter your name here",
}
),
"ticket_text": forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "Write a few words about the issue here",
"rows": "4",
}
),
}
main.js
const basicAutocomplete = document.querySelector('#basic');
const dataFilter = (value) => {
return data.filter((item) => {
return item.toLowerCase().startsWith(value.toLowerCase());
});
};
new mdb.Autocomplete(basicAutocomplete, {
filter: dataFilter
});
template
<form id="ticket_form" role="form" action="" method="post" enctype="multipart/form-data" class="">{% csrf_token %}
<div id="basic" class="form-outline mx-5 mb-2">
{{form.item}}
<label class="form-label" for="form1">Find Item</label>
</div>
#other form fields and submit button
</form>
<script>
const data = [{% for i in items %}'{{ i.item_name }} | ID: {{ i.item_id }}', {% endfor %}];
</script>
The error it's that you're creating a different string to parse the data back when you're retriving the data on the form, that the one that Django are expecting, which is the unique identifier, or your primary key.
This is more a design problem, but you have a few option available.
One could be simply, before parse the data back, when an item is selected, like for example, 'LT7 - Large Tractor 7', have a Regex
pattern to adecuate the input to what Django needs to work with your model, or even easier, split the str by -
. and take the first element, that w'd be the primary key that you need.
pk: str = input_from_form_string.split(' - ')[0] # Your LT7 will be in this variable
Is a simpler way to acomplish your objective.
I was able to fix this in the following way.
if request.method == "POST":
updated_request = request.POST.copy()
item = request.POST.get("item")
item = item.split("ID:")[1].strip()
updated_request.update({"item": item})
form = TicketSubmitForm(updated_request)
photoform = TicketImageForm(request.POST or None)
files = request.FILES.getlist("ticket_photo")
if form.is_valid():
f = form.save()
for i in files:
TicketPhoto.objects.create(ticket=f, ticket_photo=i)
messages.success(request, "Success! New Ticket Created")
return HttpResponseRedirect(reverse_lazy("home"))
else:
print(form.errors)
else:
form = TicketSubmitForm()
photoform = TicketImageForm()
context = {"form": form, "photoform": photoform, "items": list(items)}
return render(request, "home.html", context)
Thanks @AlexVergara for getting me pointed in the right direction.