# Simple Django Tip #8

### Introduction

In the past few posts, I have been sharing a couple of different tips that can be used in the starting stages of a Django project. A few of them are [how to setup pytest in a django project](https://hellosambhavi.com/simple-django-tip-3), [how to maintain secrets in a .env file](https://hellosambhavi.com/simple-django-tip-2), [how to design models the right way](https://hellosambhavi.com/simple-django-tip-4) and more.

In this post, I plan to share a super simple approach to navigate to different pages post successful login. Let me elaborate the case with a very practical scenario.

In a Django app, rather any SaaS product, when a new user signs up, a good practice is to send an activation email. Once the user activates the account, the common flow is to redirect them to the sign in page. Once the user signs in, there may be a possibility for different routes.

In some cases, the user may have to navigate to the product's home page or to a user profile page for initial onboarding. The condition may be a check on whether few user details are already captured, or an onboarding flag is set to true.

I'm sharing the approach I usually follow to navigate post successful login. It's quite simple yet clean.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">My condition may be a hypothetical one in this post, but the idea is not on the condition, but on the method to navigate to different pages.</div>
</div>

### You guessed it right 😉 Time to build

Let's perform the usual steps of creating a virtual environment, followed by installing Django, and creating a project.

```python
py -m venv venv
venv\Scripts\activate
pip install Django
django-admin startproject simplenavigation
cd simplenavigation
```

Start the development server to ensure the initial steps bring up the default page.

```python
python manage.py runserver
```

You should see the default Django page as below 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1706543655783/78d881ed-48ca-4063-b94c-10d39ec3df6a.png align="center")

### Create required apps

I usually create an app to hold user information, the `users` of the app. Once the user signs in, they will usually be redirected to the home page of the main app. I'm calling it `mymainapp`.

In order to create the two apps, execute the following command 👇

```python
python manage.py startapp users
python manage.py startapp mymainapp
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Make sure to add these two apps in your <code>INSTALLED_APPS</code></div>
</div>

### Develop CustomUser and related aspects

Let's add a CustomUser to our user model, so that we can use email as the unique identifier and the one that will be used to login instead of User name.

Prior to creating CutomerUser, we need to create a corresponding Manager that has the logic to create user as well as superuser.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">These steps are quite common across any standard Django project 😎</div>
</div>

Here is how my CustomUserManager looks like 👇

```python
from django.contrib.auth.base_user import BaseUserManager


class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifier
    for authentication instead of username
    """

    def create_user(self, email, password, **extra_fields):
        """
        Create and save a user with given email and password
        """
        if not email:
            raise ValueError("The email must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a superuser with given email and password
        """
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True")

        return self.create_user(email, password, **extra_fields)
```

And here is how my `models.py` looks like after adding CustomUser

```python
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models

from .managers import CustomUserManager


# Create your models here.
class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    display_name = models.CharField(max_length=20, \
                            null=True, blank=True)
    first_name = models.CharField(max_length=150, \
                            null=True, blank=True)
    last_name = models.CharField(max_length=150, 
                            null=True, blank=True)
    avatar = models.ImageField(upload_to="avatars/", \
                            null=True, blank=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    date_joined = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []
    
    objects = CustomUserManager()

    def __str__(self):
        return f"{self.email}"
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Since we make use of CustomUser, we need to ensure Django knows about this class to use for authentication. In order to do that, go to <code>settings.py</code> and add <code>AUTH_USER_MODEL = "users.CustomUser"</code></div>
</div>

Let's add a form to capture the `email` and `password`. This is required as otherwise the default form will look for Username and not email.

Add the following in `users/forms.py 👇`

```python
from django import forms

class SignUpForm(forms.Form):
    email = forms.EmailField(label='Email', \
                    widget=forms.EmailInput(
                        attrs={
                        'placeholder': 'your.email@example.com',
                        'required': 'required'
                        }
                    )
    )
    password = forms.CharField(label='Password', \
                    widget=forms.PasswordInput(
                        attrs={
                        'placeholder': '**********',
                        'required': 'required'
                        }
                    )
    )
```

Now to the next step as far as backend is concerned, a method for signup in our `views.py`

Go to `users/views.py` and add the following 👇

```python
from django.contrib.auth import login
from django.db import IntegrityError
from django.shortcuts import redirect, render


from .forms import SignUpForm
from .models import CustomUser


# Create your views here.
def signup(request):
    """ Method for signing up a new user.
    """
    
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            try:
                user = CustomUser.objects.create_user(
                    email=form.cleaned_data['email'],
                    password=form.cleaned_data['password'],
                )
                login(request, user)
                return redirect('mymainapp:wherenext')
            except IntegrityError:
                return render(request, 'users/signup.html', \
                              {'form': form, 'error': 'Email already exists'})
        else:
            return render(request, 'users/signup.html', {'form': form})
    else:
        return render(request, 'users/signup.html', {'form': form})
```

What we do in the method is:

1. Create a user in the database with user entered email and password
    
2. Invoke Django's default login method by passing in the user details
    
3. On successful login, we invoke a method that decides where to go next
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">At this stage you may face compile time error as we are yet to write the method <code>mymainapp/wherenext</code>. Ignore the error for the moment as we shall do it as the next step. Similarly with the template files.</div>
</div>

Let's now add the route for the newly created `signup` method. Create a file `urls.py` under `users` and add the following 👇

```python
from django.urls import path
from . import views

app_name = 'users'

urlpatterns = [
    path('signup/', views.signup, name='signup'),
]
```

As a matter of habit, I immediately register the newly created `CustomUser` in `admin.py` like so 👇. This helps me verify changes in my Django admin site.

```python
from django.contrib import admin

from .models import CustomUser

# Register your models here.
admin.site.register(CustomUser)
```

### Let's add required logic in mymainapp

In `mymainap/views.py` add a method that checks for a condition and navigates to different pages.

In reality we show a different page when the user signs in for the first time based on few attributes. Or, show additional components like "Take a tour" type features. Whereas, if they are returning users, we may want to show a different page with a few features turned off.

I generally write a method `wherenext` and add different conditions and redirect the user to different pages. I find it very simple, one place for all navigation post login. In any case of changes, I just have to change at one place.

In this post, instead of keeping it close to real-time behavior, I tweaked it for fun 🤠. A hypothetical scenario that checks whether today is a weekday or weekend and navigates to different pages that have different content.

```python
from django.shortcuts import render

from datetime import datetime

# Create your views here.
def wherenext(request):
    today = datetime.today()

    if today.weekday() in [0, 1, 2, 3, 4]:
        return render(request, 'mymainapp/weekday.html')
    else:
        return render(request, 'mymainapp/weekend.html')
```

You may want to modify this method to suit your business scenario. Everything else will more or less be the same in any case, a separate app for users and for the actual functionality and so on.

Add a file `urls.py` under `mymainapp` in order to add the routes

```python
from django.urls import path
from django.views.generic import TemplateView

from . import views

app_name = 'mymainapp'

urlpatterns = [
        path("home/", TemplateView.as_view(template_name="mymainapp/home.html"), name="home"),
        path("wherenext/", views.wherenext, name="wherenext")
]
```

That's all. The only pending step is developing the templates. I don't intend to show the entire code corresponding to `htmls` as it will unnecessarily grow the post.

Here is the link to [my Github repo](https://github.com/SambhaviPD/simple-navigation) which has the complete code. Feel free to do a `git clone`

### A few screenshots

Here's the Sign up page 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1706689713441/ec1f8785-7018-4e54-8422-407474db7b1c.png align="center")

Clicking on Sign up brings this page 👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1706689745975/1e5d8772-a753-446d-a426-45d6ed19bdac.png align="center")

Giving a dummy email

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1706689798596/e64ec43d-ad5e-433d-8bd0-a49c33820eb1.png align="center")

Since today is Wednesday, I get this 👇. Don't mind the random text please 😁

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1706689831766/4e638f31-876f-4bba-b3d1-5bf682df9706.png align="center")

### Conclusion

This is a super simple post where I shared an approach that I take when I have to navigate to different pages post login.

Probably it's a very evident approach, still when there are multiple conditions to understand which stage a user is in and then showing them the right page may lead to unnecessary complicated approaches. At such times, this simple post may be of some help 😀

### References

[https://copyprogramming.com/howto/django-conditional-login-redirect](https://copyprogramming.com/howto/django-conditional-login-redirect)
