Search code examples
pythondjangodjango-viewsdjango-forms

How can I change form field population *after* successful POST request for Class-Based-Views


I want to change the forms initial field population. Here is the catch: I want to change it for the POST request and not for the GET request.

Let's say my app offers a calculation based on a body weight input. If the user inputs a body weight calculation happens with that weight, if not a default of 50 is used. So it is allowed to let the field empty. But after the calculation ran, I want to display that the value used for calculation was 50. That's why I want to populate the field with 50 after the form was initially submitted with an empty value.

# forms.py
class CalculatorForm(forms.Form):
    body_weight = forms.FloatField(required=False)

    def clean(self):
        cleaned_data = super().clean()
        if cleaned_data['body_weight'] is None:
            cleaned_data['body_weight'] = 50.0
        # ideally I want to put the logic here
        # self.data[bpka] = 50.0 --> AttributeError: This QueryDict instance is immutable
        # self.fields['body_weight'] = 50.0 --> does not work
        # self.initial.update({'body_weight': 50.0}) --> does not work
        return cleaned_data

I feel like the problem is that I can not reach the bound fields from the clean() method.

If it is not possible within the form the logic has to go to the view I guess. Feel free to modify here:

class CalculatorView(FormView):
    template_name = 'myapp/mycalculator.html'
    form_class = CalculatorForm

    def get_context_data(self, res=None, **kwargs):
        context = super().get_context_data(**kwargs)
        if res is not None:
            context['result'] = res
        return context

    def form_valid(self, form, *args, **kwargs):
        # form['body_weight'] = 50.0 --> does not work
        res = form.cleaned_data['body_weight'] / 2
        return render(
            self.request,
            self.template_name,
            context=self.get_context_data(res=res, **kwargs)
        )

Solution

  • You can override .form_valid since the method is called when valid form data has been POSTed. In this way creating a new instance of CalculatorForm with the desired initial value and instead of returning a HttpResponseRedirect which is default render the page with the new form.

    Keep the clean method of your form as is and then pass the modified form.cleaned_data as the initial dict for your newly instantiated form.

    forms.py

    class CalculatorForm(forms.Form):
        body_weight = forms.FloatField(required=False)
    
        def clean(self):
            cleaned_data = super().clean()
            if cleaned_data['body_weight'] is None:
                cleaned_data['body_weight'] = 50.0
            return cleaned_data
    

    views.py

    class CalculatorView(FormView):
        template_name = "myapp/mycalculator.html"
        form_class = CalculatorForm
    
        def get_context_data(self, res=None, **kwargs):
            context = super().get_context_data(**kwargs)
            if res is not None:
                context['result'] = res
            return context
        
        def form_valid(self, form):
            body_weight = form.cleaned_data["body_weight"]
    
            res = body_weight / 2
    
            form = self.form_class(initial=form.cleaned_data)
            return render(
                self.request, 
                self.template_name, 
                context=self.get_context_data(res=res, form=form, **kwargs)
            )
    

    myapp/mycalculator.html

    <body>
        <form action="{% url  'calculator' %}" method="post">
            {% csrf_token %}
            {{ form }}
            <button type="submit">Send</button>
        </form>
        {% if result %}
            <p>{{ result }}</p>
        {% endif %}
    </body>