'Python Django error "Select a valid choice. That choice is not one of the available choices."

I am relatively new to Django so I am likely doing something obviously wrong but I cant seem to figure out what exactly. I have a model called Report and this has a number of fields including a foreign key relationship to my Plot model. In my Report model form I have extra fields that are not part of the model but are used to filter/search for the correct plot. I am using javascript to fetch these values on change of the respective field.

First the user selects a developer from a list (this is configured as a ModelChoice field and is populated on initialisation using a query set), this then populates a list of developments owned by the developer, they then select the desired development, this yields all plots that are part of that development, finally they select the desired plot. When the form is submitted I receive "Select a valid choice. 37d86254-29c5-456d-8264-f47e893ae789 is not one of the available choices." for both the development and plot_id fields.

My aim is to use the plot_id value (plot pkid) to obtain an instance of the respective plot in the view and store this in the report.plot foreign key relationship as per the model. I am doing is this way because I dont believe it is possible to obtain a queryset via a javscript fetch and populate the form with these values dynamically. Any help / advice will be gratefully received.

Report Form -

class CreateSnagReportForm(ModelForm):
    class Meta:
        model = Report
        exclude = (
            "developer",
            "development",
            "plot_id",
            "created",
            "id",
            "active",
            "accepted",
        )
        widgets = {
            "description": forms.TextInput(
                attrs={
                    "class": "form-control",
                    "placeholder": "Plot & Development",
                }
            ),
            "customer_name": forms.TextInput(
                attrs={"class": "form-control", "placeholder": "Customer Name"}
            ),
            "customer_email": forms.TextInput(
                attrs={"class": "form-control", "placeholder": "Customer Email"}
            ),
            "customer_service_name": forms.TextInput(
                attrs={
                    "class": "form-control",
                    "placeholder": "Customer Service Contact Name",
                }
            ),
            "customer_service_email": forms.TextInput(
                attrs={
                    "class": "form-control",
                    "placeholder": "Customer Service Contact Email",
                }
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields["developer"] = forms.ModelChoiceField(
            queryset=DeveloperOrg.objects.filter(development__isnull=False)
            .distinct()
            .order_by("name"),
            required=True,
            widget=forms.Select(
                attrs={
                    "class": "form-select",
                    "placeholder": "Developer",
                    "id": "developerName",
                    "onchange": "selectDevelopment()",
                }
            ),
        )
        self.fields["development"] = forms.ChoiceField(
            widget=forms.Select(
                attrs={
                    "class": "form-select",
                    "placeholder": "Select Development",
                    "id": "developmentName",
                    "onchange": "selectPlot()",
                }
            ),
        )
        self.fields["plot_id"] = forms.ChoiceField(
            widget=forms.Select(
                attrs={
                    "class": "form-select",
                    "placeholder": "Select Plot",
                    "id": "plotNumber",
                }
            ),
        )

Report Model -

class Report(models.Model):
    description = models.CharField(max_length=500, blank=False, null=False)
    customer_name = models.CharField(
        max_length=200, unique=False, blank=True, null=True
    )
    customer_email = models.EmailField(
        _("email address"),
        unique=False,
        blank=True,
        null=True,
        validators=[EmailValidator],
    )
    customer_service_name = models.CharField(
        max_length=200, unique=False, blank=True, null=True
    )
    customer_service_email = models.EmailField(
        _("email address"),
        unique=False,
        blank=True,
        null=True,
        validators=[EmailValidator],
    )
    plot = models.ForeignKey(Plot, null=True, blank=True, on_delete=models.SET_NULL)
    created = models.DateTimeField(auto_now_add=True)
    id = models.UUIDField(
        default=uuid.uuid4, unique=True, primary_key=True, editable=False
    )
    active = models.BooleanField(default=True)
    accepted = models.BooleanField(default=False)

    def __str__(self):
        return self.description

Create Report View -

def create_snag_report(request):
    form = CreateSnagReportForm()
    if request.method == "POST":
        form = CreateSnagReportForm(request.POST)
        if request.POST.get("plot_id"):
            plot = get_object_or_404(Plot, pk=request.POST["plot_id"])
            if form.is_valid():
                report = form.save(instance=plot)
                report.save()
                return redirect("list-snags-for-plot")
            print(form.errors)
            print(request.POST)
    context = {"form": form}
    return render(request, "snagging/create_snag_report.html", context)


Solution 1:[1]

Ok so I managed to figure out the issue, and it appears that the choices for the fields in development and plot if required a list of tuples where I was passing a single string (pkid).

Here is the updated view code.

def create_snag_report(request):
    form = CreateSnagReportForm()
    if request.method == "POST":
        form = CreateSnagReportForm(request.POST)
        plot_id = request.POST.get("plot_id")
        development_id = request.POST.get("development")
        if development_id and plot_id:
            form.fields["development"].choices = [(development_id, development_id)]
            form.fields["plot_id"].choices = [(plot_id, plot_id)]
            plot = get_object_or_404(Plot, pk=plot_id)
            if form.is_valid():
                print("valid")
                report = form.save(commit=False)
                report.plot = plot
                report.save()
                return redirect("list-snags-for-plot")
            print(form.errors)
            print(request.POST)
    context = {"form": form}
    return render(request, "snagging/create_snag_report.html", context)

As these fields are only required for filtering I guess I could just remove them from the form prior to valdation?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 MDawson