Preserve and display Django form inputs on POST submission

--PROBLEM--

I have created a basic calculator using Django. The app takes some basic form inputs before executing some calculations and returning the outcomes to the user, however I want the user's inputs to remain visible in the form on submission. At present the followin experience occurs.

  1. User enters values into form and submits using 'Calculate' button.

enter image description here

  1. Results are returned as expected but form and values are no longer present.

enter image description here

  1. User can press 'Calculate' button to return to start of process and blank form.

--DESIRED OUTCOME--

enter image description here

--CODE--

forms.py

from django import forms

class InvestmentForm(forms.Form):
    starting_amount = forms.FloatField()
    number_of_years = forms.FloatField()
    return_rate = forms.FloatField()
    annual_additional_contribution = forms.FloatField()

views.py

from django.shortcuts import render
from django.views import View
from .forms import InvestmentForm


class Index(View):
    def get(self, request):
        form = InvestmentForm()
        return render(request, 'loancalculator/index.html', {'form': form})

    def post(self, request):
        form = InvestmentForm(request.POST)

        if form.is_valid():


                total_result = form.cleaned_data['starting_amount']
                total_interest = 0
                yearly_results = {}

                for i in range(1, int(form.cleaned_data['number_of_years'] +1)):
                    yearly_results[i] = {}

                    # CALCULATE THE INTEREST 
                    interest = total_result * (form.cleaned_data['return_rate'] / 100)
                    total_result += interest 
                    total_interest += interest

                    # ADDITIONAL CONTRIBUTION 
                    total_result += form.cleaned_data['annual_additional_contribution']

                    # SET YEARLY RESULTS 
                    yearly_results[i]['interest']  = round(total_interest, 2)
                    yearly_results[i]['total'] = round(total_result,2)


                    # CREATE CONTEXT 
                    context = {
                        'total_result': round(total_result,2),
                        'yearly_results': yearly_results,
                        'number_of_years': int(form.cleaned_data['number_of_years'])
                    }
            
                # RENDER THE TEMPLATE

                return render(request, 'loancalculator/index.html', context)


        
        else:
            form = InvestmentForm()
            return render(request, 'loancalculator/index.html', {'form':form})
        
            
        

index.html

{% extends 'loancalculator/base.html' %}
{% load crispy_forms_tags %}

{% block content %} 


<div class="row justify-content-center">
    
    <div class="col-4 border bg-light">
    <div class="row t-5">
        <div class="col-12 col-md-6 mx-md-auto">
            <h1> Investment Calculator</h1>
                <h5> Enter the details below to see your estimate</h5>

            <form action="" method="POST">
                {% csrf_token %}
                {{ form|crispy }}
                <button class="btn btn-primary" type="submit">Calculate</button>                
            </form>
            <br>
        </div>
    </div>
    </div>

    <div class="col-4 border">
        <div class="row t-5">
            <div class="col-12 col-md-6 mx-md-auto">
                <h1>Investment Results</h1>
                <h3> After {{ number_of_years }} years, your investment is worth ${{ total_result }}</h3>
    
                <div class="mt-5">
                    <table class="table">
                        <thead>
                            <tr>
                                <th scope="col">Year</th>
                                <th scope="col">Interest</th>
                                <th scope="col">Total</th>
                            </tr>
                        </thead>
                        <tbody>
    
                            {% for key, value in yearly_results.items %}
    
                            <tr>
                                <th scope="row">{{ key }}</th>
                                <td>{{ value.interest }}</td>
                                <td>{{ value.total }}</td>
                            </tr>
                            {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
      </div>
    </div>  
  </div>

{% endblock content %}

When you create the context, the value 'form' doesn't get set (views.py, under get())

Two ways you can go about doing what you want as I see it:

Method 1
Use sessions to hold the data and then refeed it to the form using initial form values. This is probably the easiest since it most closely matches what you already have.

from django.shortcuts import render
from django.views import View
from .forms import InvestmentForm


class Index(View):
    def get(self, request):

        # Fill in the initial values on the unbound form:
        initial = {
            'starting_amount': request.session.get('starting_amount'),
            'number_of_years': request.session.get('number_of_years'),
            'return_rate': request.session.get('return_rate'),
            'annual_additional_contribution': request.session.get('annual_additional_contribution'),
        }

        form = InvestmentForm(initial=intial)
        return render(request, 'loancalculator/index.html', {'form': form})

    def post(self, request):
        form = InvestmentForm(request.POST)

        if form.is_valid():

                # After getting the cleaned_data, set a session variable to hold it
                total_result = form.cleaned_data['starting_amount']
                request.session['starting_amount'] = total_result

                number_of_years = int(form.cleaned_data['number_of_years']
                request.session['number_of_years'] = number_of_years

                return_rate = form.cleaned_data['return_rate']
                request.session['return_rate'] = return_rate

                annual_additional_contribution = form.cleaned_data['annual_additional_contribution']
                request.session['annual_additional_contribution'] = annual_additional_contribution
                
                total_interest = 0
                yearly_results = {}

                for i in range(1, number_of_years +1)):
                    yearly_results[i] = {}

                    # CALCULATE THE INTEREST 
                    interest = total_result * (return_rate / 100)
                    total_result += interest 
                    total_interest += interest

                    # ADDITIONAL CONTRIBUTION 
                    total_result += annual_additional_contribution

                    # SET YEARLY RESULTS 
                    yearly_results[i]['interest']  = round(total_interest, 2)
                    yearly_results[i]['total'] = round(total_result,2)

                    # CREATE CONTEXT 
                    context = {
                        'total_result': round(total_result,2),
                        'yearly_results': yearly_results,
                        'number_of_years': number_of_years)
                    }
            
                # RENDER THE TEMPLATE

                return render(request, 'loancalculator/index.html', context)
                
        
        else:
            form = InvestmentForm()
            return render(request, 'loancalculator/index.html', {'form':form})

Method 2
From what you show here, anyway, you could just use JavaScript to do the calculations by grabbing the values directly from the DOM after they are entered, doing the calculations on the front end with JavaScript, and then fill in the values by targeting the locations where you want the results displayed in the DOM. No need for forms, no reloading of pages.

Back to Top