djangoforeign-keysmodelform

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?

Edited to add Example: Item model has object with item_name 'Large Tractor 7', item_id 'LT7'. I want the autocomplete to populate with 'Large Tractor 7 | ID: LT7'. I can make my form work if I only display 'LT7' because the Ticket model is looking for the item foreign key(i.e. LT7), but if I create the string with more information, the form produces the "Select a valid choice" error and I can't create the ticket.

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>

Solution

  • 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.