Search code examples
djangodjango-viewsdjango-formsdjango-sessions

Django sessions not saving data from form, no error or apparent reason


I have a view with different 'stages' (as I wanted to keep the same URL without resorting to something like AJAX), which takes data from a form, does some processing, and then would save that data to the database.

Because I can't forward some data via POST, I wanted to save that data in a session.

For some reason, this is not working - at all.

I can see the data being extracted from the form, and I can forward that via POST, but attempting to save or retrieve it to a session either results in blank data, or a KeyError.

This is the view I am using (very trimmed down, but the error is reproducible)

def processpayment(request, *args, **kwargs):
    if request.method == 'POST':
        form = paymentDetailsForm(request.POST)
        amount = request.POST.get('amount', False);
        stage = request.POST.get('stage', False);  
        if (stage == "checkout"):    
            return render(request, "payment.html", {'form': form, 'amount': amount, 'stage': stage})        
            
        if (stage == "complete"):
            return render(request, "payment.html", {'amount': request.session['amount'], 'stage': stage, 'name': request.session['name']}) 
            
        if (amount == "custom"):
            if form.is_valid():
                amount = form_data.get('amount')
                name = form_data.get('name')
                request.session['amount'] = amount
                request.session['name'] = name
                request.session.modified = True
            return render(request, "payment.html", {'form': form, 'stage': stage, 'amount': amount})  
        return render(request, "payment.html", {'amount': amount, 'stage': stage})                  

    else:       
         return render(request, "payment.html")      
    return render(request, "payment.html")

This is the template I am using::

{% load static %}
{% load widget_tweaks %}

<html>

<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.2/css/bootstrap.min.css" integrity="sha512-rt/SrQ4UNIaGfDyEXZtNcyWvQeOq0QLygHluFQcSjaGB04IxWhal71tKuzP6K8eYXYB6vJV4pHkXcmFGGQ1/0w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>

<div class="m-3">

{% if stage == "complete" %}  
  
<<h2 class="mx-auto text-center mb-3">Name:<b>&nbsp; {{ name }}</h2>  

{% elif stage == "checkout" %}

<div style="margin-left: 20%; margin-right: 20%;">

    <h2 class="mx-auto text-center mb-3">Amount to be charged: ${{ amount }}</h2>  

    <div class="col-md-12 mx-auto d-flex justify-content-center mt-2">   
        <div class="container">
                <div class="row ">
                <div class="col-md-6 p-3">
                    <form id='formName' name='formName' action="{% url 'processpayment' %}" method="POST">
                    <input type="hidden" id="amount" name="amount" value="{{ amount }}">
                    <input type="hidden" id="stage" name="stage" value="complete">
                    <button type="submit" name="submit" class="btn btn-primary mt-3">Next</button>
                    </form>
                </div>
            </div>
        </div>    
    </div>

</div>

{% elif amount  == "custom" %}
    
<form action="{% url 'processpayment' %}" method="POST">    
    
    <div class="container pt-3 pb-3">  
          
        <input type="hidden" id="stage" name="stage" value="checkout">         

          <div class="row">
            <div class="col-md-6 mb-2">
              {% render_field form.amount class+="form-control" placeholder="AMOUNT" %}
              </div>
   
            <div class="col-md-6 mb-2">
            {% render_field form.name class+="form-control" placeholder="NAME" %} 
            </div>
            </div>   
          
           <div class="row justify-content-center">
             <span class="input-group-btn">
               <button class="btn btn-primary mt-3" type="submit">Next</button>
             </span>
           </div>        
      </div>

</form>

{% else %}

    <div class="col-md-12 mx-auto d-flex justify-content-center mt-2">   
      <div class="container">
      <div class="row ">
        <div class="col-md-12 text-center p-3">
        <form id='formName' name='formName' action="{% url 'processpayment' %}" method="POST">
        <input type="hidden" id="amount" name="amount" value="custom">
        <button type="submit" name="submit" class="btn btn-primary">Make a payment</button>
        </form>
        </div>
      </div>
      </div>    
    </div>
    
    
{% endif %}
    
</div>

</body>
    
</html>

And just for completeness my forms.py:

from django import forms
from django.forms import widgets
from .models import PaymentMade

class paymentDetailsForm(forms.ModelForm):
    name = forms.CharField(max_length=50)
    amount = forms.IntegerField()

    def __init__(self, *args, **kwargs):
        super(paymentDetailsForm, self).__init__(*args, **kwargs)
        self.fields['name'].required = False
        self.fields['amount'].required = False

    class Meta:
        model = PaymentMade
        fields = ('name', 'amount')

In the 'custom' stage, if I manually set data to be stored in a session, e.g.

request.session['newvar'] = "test works"

I can retrieve that data from the session without any issues at all. So it doesn't seem to be an issue with sessions as such as saving the data from a form into a session variable.

I assume I am missing something very simple, but after working on this for several hours and testing different things I cannot see what I am doing wrong.

edit: I also have the following settings enabled in my settings.py:

SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 1200 
SESSION_SAVE_EVERY_REQUEST = True

Solution

  • Based on the comments, I suspect you are not reaching your is_valid() code when you want it in your various form states. Try something like this.

    from django.contrib import messages
    
    def processpayment(request, *args, **kwargs):
        if request.method == 'POST':
            form = paymentDetailsForm(request.POST)
            amount = request.POST.get('amount', False);
            stage = request.POST.get('stage', False);  
    
            if form.is_valid():
                form_data = form.cleaned_data
                amount = form_data.get('amount')
                name = form_data.get('name')
                if amount:
                    request.session['amount'] = amount
                if name:
                    request.session['name'] = name
                if name or amount:
                    request.session.modified = True
    
                if (stage == "checkout"):    
                    return render(request, "payment.html", {'form': form, 'amount': amount, 'stage': stage})        
            
                if (stage == "complete"):
                    return render(request, "payment.html", {'amount': request.session['amount'], 'stage': stage, 'name': request.session['name']}) 
            
                if (amount == "custom"):
                    return render(request, "payment.html", {'form': form, 'stage': stage, 'amount': amount})  
            
            else:
                messages.error(request, "Your form is invalid") 
                #print errors to terminal - you can also put them in a template
                print(form.errors)
                print(form.non_field_errors)             
                return render(request, "payment.html", {'amount': amount, 'stage': stage, 'form':form})                      
    
        return render(request, "payment.html")
    

    To see messages in your template add something like:

        {% for message in messages %}
            {{ message | safe }}
        {% endfor %}