Search code examples
djangopostdjango-formshidden-fielddisabled-input

Way to pass information from a GET parameter to a POST form in django?


I have a mailing list system in which a user can click a link to unsubscribe. This link contains the user's email in a GET parameter and the page it points to contains a short form to ask for feedback. This feedback needs to point to the email of the user who submitted it.

The way I tried to achieve this is:

  1. take the email from the GET parameter
  2. put it as initial value in a hidden field on the feedback form
  3. retrieve it from form data when the form is sent

The problem is that if this hidden field is not disabled, the user can meddle with its value and dissimulate his own identity or even claim that the feedback came from another user. But if I set the field as disabled, the request.POST dictionary does not contain the field at all.
I also tried keeping the field enabled and checking for its presence in form.changed_data, but it seems to always be present there even if its value does not change.

This is the form class:

class UnsubscribeForm(forms.Form):
    reason = forms.ChoiceField(choices=UnsubscribeFeedback.Reasons.choices)
    comment = forms.CharField(widget=forms.Textarea, required=False)
    user_email = forms.CharField(widget=forms.HiddenInput, required=False, disabled=False)

This is how I populate user_email in the view when the method is GET:

email = request.GET.get("email", "")
# ...
context["form"] = UnsubscribeForm(initial={"user_email": email})

Note that I also tried disabling the field manually after this line, as well as in the form's init method. The result is the same: if the field is disabled, the value does not get passed.
After setting the initial value, I print()ed it to make sure it was being set correctly, and it is. I also checked the page's source code, which showed the value correctly.

And this is how I check for the value in the POST part of the view, when the data-bound form is being received:

form = UnsubscribeForm(request.POST)

if form.is_valid():  # This passes whether I change the value or not.
    if "user_email" in form.changed_data:  # This too passes whether I change the value or not.
        print("Changed!")

    email = form.cleaned_data["user_email"]  # This is "" if user_email is disabled, else the correct value.

I have no idea why the initial value I set is being ignored when the field is disabled. As far as I know, a disabled field passes the initial value over regardless of any changes, but here the initial value isn't being passed at all. And as I outlined above, I can't afford to keep this field editable by the user, even if it's hidden.

Django is version 3.0.3, if that matters.

Any solution? Is this a bug?


Solution

  • I found a solution to my problem, though it doesn't quite answer the question of why disabled fields ignore runtime initial values, so in a sense, the question is still open to answers.

    In the original question, I crucially neglected to specify (in an effort to make the code minimal and reproducible) that the GET request that includes the user's email address also contains a token I generate with unpredictable data to verify that the email is authentic and corresponds to a subscribed user. In order to successfully meddle with the email, a user would also have to forge a valid token, which is unlikely (and not worth the effort) unless they have access to both my database and codebase (in which case I have worse problems than a feedback form).

    I will simply keep the hidden field not disabled and also pass the token along, to verify that the address is indeed valid.