Forums

Key error in session variables

My website uses session data while registering new users and verifying them using OTP sent to their contact numbers, so that i can use the same phone number that was verified before in the registration form where they fill up more details like their name, email and password. So the phone number is stored in a session variable name 'phone'. For some users it works fine as expected, i.e: no errors, but for some users, when they fill up the registration form and click submit, it throws a key error in my views file at 'phone'(debug is set to TRUE for testing). I don't know what's happening, i'm using default settings for the session engine and default middleware. The sad part is, i can't even find what's wrong because it works fine for some users(worked fine for me when i tried out on different devices). What could be wrong here, is it a browser specific problem, or is it a problem with database or the session engine?

You could catch this error and investigate the session state when it occurs. Also, you could add some logging where the phone key should be set. It might help narrowing the investigation.

The error occurs when the user sends a POST request on the registration page, that means the 'phone' key was available when the user was shown the registration form in the first place(GET request), but when the user submits the form, there's a key error on 'phone'. Why does that happen on a POST request, also to mention that only 3 of the 11 people got that error, for the rest, it was working fine. I was not able to find out the source of the error. And one other detail, the error only occurs on mobile browsers(some, not all). None of the pc/laptop users reported the error. Could it be an issue with mobile browser or the settings of the browser that erases session data. Or could it be the session_cookie_age. Maybe it expires if users take too long to fill out the form. I haven't set session_cookie_age in my settings.py file though.

How are you managing the session? Since you're on a paid account, your web app has at least 2 workers available, that means that each request can be managed by a different process which is agnostic about the state of the session. It could be a lottery, and that might explain why only some of your users experienced that.

I am using the default settings for sessions. SESSION_ENGINE = "django.contrib.sessions.backends.db". The session data is stored in my database in django_session table. If it is a web worker problem, does it occur with database backed sessions or not? If yes, then what is the solution for it.

[edited by admin]

def register_builder(request):
    verified_phone = request.session['phone']
    context = {'phone': request.session['phone']}
    if request.POST:
        form = BuilderRegistrationForm(request.POST)
        # this is to confirm that the user does not change the value of the phone number and
        # only uses the verified one
        phone1 = form['phone'].value()

    if form.is_valid() and request.session['verified']==1 and verified_phone==phone1:
        user=form.save()
        user.is_builder = True
        user.save()
        phone = form.cleaned_data.get('phone')
        raw_password = form.cleaned_data.get('password1')
        account = authenticate(phone=phone, password=raw_password, is_builder=True)
        login(request, account)
        return redirect('builder_index')

    else:
        context['registration_form']=form
else:
    form = BuilderRegistrationForm()
    form['phone'].initial=request.session['phone']
    context['registration_form'] = form
return render(request, 'accounts/register_builder.html',context)

Here's another interesting thing, although it shows a key error in the first line of the function when a POST request is made, it actually registers the user successfully. So if they try to login after the error, they can.

if you use .get("foo") instead of ["foo"] to access something that does not exist you will not get a key error but None will be returned.

ok, but what happened to the variable in session?

Thanks for flagging the error though, i used the .get('foo'), but I still can't figure out what happened to my session data?

There are 2 possible reasons that I can think of:

  1. The session changed, so you were trying to get data from a session that had not been initialised
  2. There is a way to access your web app that means that you get to the code that looks up the session data before you have been through the code that sets it, so it's not there yet.

for the second reason, i can assure you that the session variable was set, because if not, then the user would not even see the registration view. Here's the complete code.

    # ----------  REGISTER A BUILDER  ------------
# enter the phone no to verify to continue with registration

def mobileDetail_builder(request):
    context = {}
    logout(request)
    if request.POST:
        # setting this to 0 ensures that upon entering the phone number, user must verify the otp
        # to set it to 1.
        request.session['verified'] = 0
        mobile_form = mobileForm(request.POST)
        if mobile_form.is_valid():
            phone = mobile_form.cleaned_data.get('phone')
            Status, id = send_otp(phone)
            if Status == 'Success':
                request.session['id']=id
                request.session['phone']=phone
                return redirect('otp_builder')
            else:
                messages.error(request,'OTP NOT SENT, PLEASE TRY AGAIN WITH VALID PHONE NUMBER')
                return redirect('mobile_verify_builder')
        else:
            context['mobile_form'] = mobile_form
    else:
        mobile_form = mobileForm()
        context['mobile_form'] = mobile_form
    return render(request,'accounts/mobile.html',context)

# verify otp

def otp_verify_builder(request):
    context = {}
    id = request.session['id']
    print(id)
    if request.POST:
        otp_form = otp_verification_form(request.POST)
        if otp_form.is_valid():
            otp = otp_form.cleaned_data.get('otp')
            status = check_otp(otp,id)
            if status == 'Success':
                # this is to ensure that if user goes directly to the url of registration after
                # entering the phone no, he/she cant register without the otp verification
                request.session['verified'] = 1
                return redirect('register_builder')
            else:
                raise ValueError('ENTER CORRECT OTP')
        else:
            context['otp_form'] = otp_form
    else:
        otp_form = otp_verification_form()
        context['otp_form'] = otp_form
    return render(request,'accounts/otp_builder.html',context)

def register_builder(request):

    verified_phone = request.session['phone']
    context = {'phone': request.session['phone']}
    if request.POST:
        form = BuilderRegistrationForm(request.POST)
        # this is to confirm that the user does not change the value of the phone number and
        # only uses the verified one
        phone1 = form['phone'].value()

        if form.is_valid() and request.session['verified']==1 and verified_phone==phone1:
            user=form.save()
            user.is_builder = True
            user.save()
            phone = form.cleaned_data.get('phone')
            raw_password = form.cleaned_data.get('password1')
            account = authenticate(phone=phone, password=raw_password, is_builder=True)
            login(request, account)
            return redirect('builder_index')

        else:
            context['registration_form']=form
    else:
        form = BuilderRegistrationForm()
        form['phone'].initial=request.session['phone']
        context['registration_form'] = form
    return render(request, 'accounts/register_builder.html',context)

So, the view mobileDetail_builder inputs the phone number and sends otp to that phone number, thats when the 'phone' key is set in the session, if successful, it redirects user to the otp_verify_builder view which lets user enter the otp sent to their phone. If this is successful, that's when the user is redirected to the registration view. So the 'phone' key is already set in the previous views, and it is used the registration form to automatically show the user their phone number. If you reload the registration form, it will still show the user their phone number, that means the 'phone' key is still in the session. The error occurs(only for some users) when the user submits the registration form(POST request).

Is it possible that django delete session data once a user logs in? because in my registration view, the user is logged in once they fill out the form. Still no reason to show error on the first line of the register_builder view, which comes before the POST request code. This has got me confused....

And as i mentioned earlier, the POST request is successfully completed, means the user is successfully registered, but it still shows up the key error(in the first line of the view where verified_phone is set), the line that comes before the POST request code.

You can add some logging to your code and compare session ids?

I compared the two session_key before and after the user registration(I did not get the error after i registered). So the session key changes when the post request is completed(since it logs in the user). But I can still access the 'phone' key that was there before it logged the user in, i.e: with the previous session_key.

It does make a certain amount of sense that the session key (and thus the associated data) will change when the user logs in. So perhaps for information that you want to have persist over that change, you could store it on the user object itself? For our own registration system, for example, we store the email address and so on on the user (and I guess you probably do too), so as long as the user object is still accessible after the login, you can get the phone number that way.

Yes, I do use the user model for user info like phone, email. But before they register, i save it in a session to verify them with OTP. The error occurs after the POST request to register a new user. I think it's the logging in that causes the session data to change on some devices, that's why it shows the key error. But i have used the request.session.get method(which returns None if that particular session variable is not present in session) now as suggested before, and i believe it won't cause any errors now since the user is successfully registered anyway and i don't need the 'phone' key after they register. So the .get() method won't show any errors when the 'phone' key changes after registration. Let's hope so.

If you don't require the phone to have a specified value nor want to have more registered users, that should work.

Actually I do want more registered users. You can see in the code snippet that 'phone' key is only required upto the point of verifying a new user and showing them with a registration form, after successful registration, I can just access the phone number from the user model with request.user.phone.

I'm using a Mobil phone but each time I open a console and start typing codes the interpreter gives me error syntax, does it mean this site don't work with mobile phones?

It's the keyboard sending extra characters for auto-completion. Use a keyboard that does not do that, for example "Hacker's keyboard" on Android.