Why *dynamic* form field values being lost if form invalid?

Normally, if a form is invalid, the invalid submitted values persist in the invalid form so the user can correct and resubmit.

I have a form with dynamic values that get lost if the form is invalid? Why? How fix?

When I say dynamic, what I mean is that they were defined in the form constructor like so…

class RequestApproveForm(django.forms.Form):
def init(self, *args, **kwargs):
super().init(*args, **kwargs)

            ...some code...

            self.fields[<field name>] = <field value>
               
            ....etc.

In order for us to be able to provide an explanation, we’d need to see the complete form and the view in which it is being used.

Ken

Thank you. Here is the form code…

...
CHOICE         = django.forms.ChoiceField
...
VAL_ERR        = django.forms.ValidationError
...
class RequestApproveForm(django.forms.Form):
        def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                rsings  = grandmas4hire.controllers.get_rsings()
                rsets   = grandmas4hire.controllers.get_rsets()
                provs   = grandmas4hire.bighelp.provs()
                provs   = [(e[0], f"{e[1]} {e[2]}") for e in provs]
                provs   = [("0", "None selected.")] + provs
                p_field = CHOICE(choices = provs,   initial = "0")
                windows = list(grandmas4hire.controllers.WINDOW.items())
                w_field = CHOICE(choices = windows, initial = "1.0")
                for e in rsings:
                        self.fields[f"rsing_{e.id}_w"] = w_field
                        self.fields[f"rsing_{e.id}_p"] = p_field
                for e in rsets:
                        self.fields[f"rset_{e.id}_w"] = w_field
                        self.fields[f"rset_{e.id}_p"] = p_field

        def clean(self):
                unavail = False
                for e in grandmas4hire.controllers.get_rsings():
                        w        = self.cleaned_data[f"rsing_{e.id}_w"]
                        p        = self.cleaned_data[f"rsing_{e.id}_p"]
                        unavail_ = grandmas4hire.controllers.unavail(e.id,
                                                                     None,
                                                                     p,
                                                                     w)
                        if unavail_:
                                unavail = unavail_
                                prov    = grandmas4hire.bighelp.prov_name(p)
                                break
                for e in grandmas4hire.controllers.get_rsets():
                        w        = self.cleaned_data[f"rset_{e.id}_w"]
                        p        = self.cleaned_data[f"rset_{e.id}_p"]
                        unavail_ = grandmas4hire.controllers.unavail(None,
                                                                     e.id,
                                                                     p,
                                                                     w)
                        if unavail_:
                                unavail = unavail_
                                prov    = grandmas4hire.bighelp.prov_name(p)
                                break
                if unavail:
                        date = unavail[0].strftime("%-m/%-d/%Y")
                        time = datetime.time(hour = unavail[1], minute = 0)
                        time = time.strftime("%-I:%M %p")
                        msg  = f"{prov} is unavailable at {time} on {date}."

                        raise VAL_ERR(msg)

Here is the function that uses it…

def manage_requests(request):
        rsings = get_rsings()
        rsets  = get_rsets()
        if request.method == "POST":
                form  = grandmas4hire.forms.RequestApproveForm(request.POST)
                if form.is_valid():
                        reply = manage_requests_post(form, rsings, rsets)
                else:
                        reply = django.shortcuts.render(request,
                                                        "manage_requests.html",
                                                        {"form"   : form,
                                                         "rsings" : rsings,
                                                         "rsets"  : rsets})
        else:
                msg   = request.GET.get("msg")
                form  = grandmas4hire.forms.RequestApproveForm()
                for e in rsings:
                        setattr(e, "w", form.fields[f"rsing_{e.id}_w"])
                        setattr(e, "p", form.fields[f"rsing_{e.id}_p"])
                for e in rsets:
                        setattr(e, "w", form.fields[f"rset_{e.id}_w"])
                        setattr(e, "p", form.fields[f"rset_{e.id}_p"])
                reply = django.shortcuts.render(request,
                                                "manage_requests.html",
                                                {"form"   : form,
                                                 "rsings" : rsings,
                                                 "rsets"  : rsets,
                                                 "msg"    : msg})

        return reply

Here is the helper function…

def manage_requests_post(form, rsings, rsets):
        msg   = "No request(s) approved."
        for e in rsings:
                p = form.cleaned_data[f"rsing_{e.id}_p"]
                w = form.cleaned_data[f"rsing_{e.id}_w"]
                if p != "0":
                        msg = "Request(s) approved."
                        manage_requests_execute(e.id, None, p, w)
        for e in rsets:
                p = form.cleaned_data[f"rset_{e.id}_p"]
                w = form.cleaned_data[f"rset_{e.id}_w"]
                if p != "0":
                        msg = "Request(s) approved."
                        manage_requests_execute(None, e.id, p, w)
        url   = "/manage_requests"
        url   = add_url_param(url, "msg", msg)
        reply = django.shortcuts.redirect(url)

        return reply

Tell me if you need anything else.

Thanks again.

Chris

I can’t recreate the symptoms from the fundamental statement you’ve made here.

As a simple test case, I’ve created a script in my “dt” project:

import django
from django.conf import settings
import dt
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dt.settings')

settings.configure()
django.setup()

from django.forms import Form, CharField

class MyForm(Form):
    f1 = CharField(max_length=4)
    f2 = CharField(max_length=4)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['f3'] = CharField(max_length=4)

print('*'*10, 'Test 1', '*'*10)
aform = MyForm({'f1':'a', 'f2':'b', 'f3':'c'})
print(aform.is_valid())
print(aform.cleaned_data)
print(aform)

print('\n\n\n', '*'*10, 'Test 1', '*'*10)

aform = MyForm({'f1':'a', 'f2':'abcde', 'f3':'c'})
print(aform.is_valid())
print(aform.cleaned_data)
print(aform)

Generates the following output:
(I have removed the excess blank lines)

********** Test 1 **********
True
{'f1': 'a', 'f2': 'b', 'f3': 'c'}
<div>
    <label for="id_f1">F1:</label>
<input type="text" name="f1" value="a" maxlength="4" required id="id_f1">
</div>
  <div>
    <label for="id_f2">F2:</label>
<input type="text" name="f2" value="b" maxlength="4" required id="id_f2">
</div>
  <div>
    <label for="id_f3">F3:</label>
<input type="text" name="f3" value="c" maxlength="4" required id="id_f3">
</div>
 ********** Test 2 **********
False
{'f1': 'a', 'f3': 'c'}
<div>
    <label for="id_f1">F1:</label>
<input type="text" name="f1" value="a" maxlength="4" required id="id_f1">
</div>
  <div>
    <label for="id_f2">F2:</label>
<ul class="errorlist"><li>Ensure this value has at most 4 characters (it has 5).</li></ul>
<input type="text" name="f2" value="abcde" maxlength="4" required aria-invalid="true" id="id_f2">
</div>
  <div>
    <label for="id_f3">F3:</label>
<input type="text" name="f3" value="c" maxlength="4" required id="id_f3">
</div>

The dynamically added f3 does retain its value in both cases (valid and invalid form).

This leads me to believe that the issue is in your form or view, and not within the Django forms themselves.

If I had to try and debug this, I’d either add a bunch of print statements in this to try and determine exactly what’s happening, or to run this in the debugger.