How to convert a base64 user ID to an object friendly string

I have these views for registering and activating new user accounts:

from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.sites.shortcuts import get_current_site
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes, force_str
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode

#from orders.views import user_orders

from .forms import RegistrationForm
from .models import UserBase
from .tokens import account_activation_token

def account_register(request):
    
    if request.method == 'POST':
        registerForm = RegistrationForm(request.POST)
        if registerForm.is_valid():
            user = registerForm.save(commit=False)
            user.email = registerForm.cleaned_data['email']
            user.set_password(registerForm.cleaned_data['password'])
            user.is_active=False
            user.save()
            #E-mail setup
            current_site = get_current_site(request)
            subject = 'Activate your Account'
            message = render_to_string('users/registration/account_activation_email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'token': account_activation_token.make_token(user),
            })
            user.email_user(subject=subject, message=message)
            return HttpResponse('registered succesfully and activation sent')
        
    else:
        registerForm = RegistrationForm()
    return render(request, 'users/registration/register.html', {'form': registerForm})

def account_activate(request, token, uidb64):
    try:
        uid = force_str(urlsafe_base64_decode(uidb64))
        print('uid:', uid)
        user = UserBase.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, UserBase.DoesNotExist):
        user = None
    print(user)
    print(token)
    print(account_activation_token.check_token(user, token))
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        login(request, user)
        return redirect('users:dashboard')
    else:
        return render(request, 'users/registration/activation_invalid.html')

@login_required
def dashboard(request):
    orders = user_orders(request)
    return render(
        request,
        'users/user/dashboard.html',
        {'section': 'profile', 'orders': orders}
    )

Since this is my first project, I’m using a tutorial to teach me the ropes. It uses a base64-encoded ID as well as a custom token to generate an activation link. The method used to decode the user ID to a URL friendly format works fine, however to convert it to a format that can be used by python to fetch the user object they used force_text on the decoding function, which was removed from Django some time ago.

In my current account_activate() view, Django is able to decode uid (the user ID originally encoded in base64), but it’s not in a format that UserBase.objects.get(pk=uid) can’t recognize, and therefore the exception is executed and user becomes None. I found on the internet that, since force_text() has been removed I should use force_str(), but in my code it can’t process the decoded uid. Is there something else I can use to get the user object from the decoded ID?

The corresponding line of code used by Django in the password reset process is:
uid = urlsafe_base64_decode(uidb64).decode()

I just tried that in my code. Deleted the previous user and created a new one, but the user variable is still None, and uid seems to not be getting decoded at all.

Also I’m not sure to what degree this applies, but my case is not about password resetting, it’s fetching the user object using uid to activate their account. The updated view:

def account_register(request):
    
    if request.method == 'POST':
        registerForm = RegistrationForm(request.POST)
        if registerForm.is_valid():
            user = registerForm.save(commit=False)
            user.email = registerForm.cleaned_data['email']
            user.set_password(registerForm.cleaned_data['password'])
            user.is_active=False
            user.save()
            #E-mail setup
            current_site = get_current_site(request)
            subject = 'Activate your Account'
            message = render_to_string('users/registration/account_activation_email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'token': account_activation_token.make_token(user),
            })
            user.email_user(subject=subject, message=message)
            return HttpResponse('registered succesfully and activation sent')
        
    else:
        registerForm = RegistrationForm()
    return render(request, 'users/registration/register.html', {'form': registerForm})

def account_activate(request, token, uidb64):
    try:
        print(urlsafe_base64_decode(uidb64).decode())
        uid = urlsafe_base64_decode(uidb64).decode()
        print('uid:', uid)
        user = UserBase.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, UserBase.DoesNotExist):
        user = None
    print(user)
    print(token)
    print(account_activation_token.check_token(user, token))
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        login(request, user)
        return redirect('users:dashboard')
    else:
        return render(request, 'users/registration/activation_invalid.html')

I understand that, but you’re using effectively the same process as Django’s password reset process.

What are your print statements showing?

Also, what does your account_activation_email.html file look like?

What does the urls.py definition look like for the account_activate view?

My print statements are showing None (print(user)), e (print(token)), and False (print(account_activation_token.check_token(user, token))). The print statements within the try block aren’t returning anything.

account_activation_email.html:

{% autoescape off %}
Hello, {{ user.user_name }}!

Your account has been successfully created. Please click the link below to activate your account.

http://{{ domain }}{% url 'users:activate' uidb64=user token=token %}

{% endautoescape %}

urls.py:

from django.urls import path

from . import views

app_name = 'users'

urlpatterns = [
    path('register/', views.account_register, name = "register"),
    path('activate/<slug:uidb64><slug:token>/', views.account_activate, name = "activate"),
    path('dashboard/', views.dashboard, name='dashboard'),
]

You do not have a / between your parameters in the url definition. I’m not sure how the dispatcher is going to handle that regarding those two fields.

OK I figured out the issue. Turns out in account_activation_email.html I was passing the user into uidb64 instead of the pk. http://{{ domain }}{% url 'users:activate' uidb64=user token=token %} should instead be http://{{ domain }}{% url 'users:activate' uidb64=uid token=token %}

1 Like

Hey there. I am having the same issue as this person, but when I do his solution, I get a namespace error:

‘accounts’ is not a registered namespace

I see it is receiving the information from the activation link but on this code:

uid = force_str(urlsafe_base64_decode(uidb64))

it gives the error:

Field ‘id’ expected a number but got b’<property object at 0x000002553FA59DA0>'.

The above exception (invalid literal for int() with base 10: b’<property object at 0x000002553FA59DA0>') was the direct cause of the following exception:

Any help would be greatly appreciated.

Ok, I looked at it again and I had the correct account_activation_email.html with the uidb64=uid, but did not have the users, I am using accounts, and when I do this I get the namespace issue.

###################################################################
Added namespace accounts

This is accounts/urls

app_name = ‘accounts’

urlpatterns = [
path(‘signup/str:alias_name’, views.SignUpView.as_view(), name=‘signup.html’),
path(‘activate///’, views.activate, name=‘activate’),
path(‘account_activation_sent/’, views.account_activation_sent, name=‘account_activation_sent’),
path(‘account_activation_complete/’, views.account_activation_complete, name=‘account_activation_complete’),

Error:
Reverse for ‘account_activation_sent’ not found. ‘account_activation_sent’ is not a valid view function or pattern name.

I suggest you open a new topic for this issue.

Also, when you’re posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. Do this around the contents of each file, error message, template, traceback, etc.