27 : User Registration

Well that was pretty good, Now, our users will see the details of a job post. But what if someone wants to create a job post? Do you remember,each job post should have an owner. That means we need to provide our users a way to register and login. In this post, we are going to see the signup/registration part. Lets put our code for user registration in a new file webapps > users > route_users.py

from db.repository.users import create_new_user
from db.session import get_db
from fastapi import APIRouter
from fastapi import Depends
from fastapi import Request
from fastapi import responses
from fastapi import status
from fastapi.templating import Jinja2Templates
from schemas.users import UserCreate
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from webapps.users.forms import UserCreateForm


templates = Jinja2Templates(directory="templates")
router = APIRouter(include_in_schema=False)


@router.get("/register/")
def register(request: Request):
    return templates.TemplateResponse("users/register.html", {"request": request})


@router.post("/register/")
async def register(request: Request, db: Session = Depends(get_db)):
    form = UserCreateForm(request)
    await form.load_data()
    if await form.is_valid():
        user = UserCreate(
            username=form.username, email=form.email, password=form.password
        )
        try:
            user = create_new_user(user=user, db=db)
            return responses.RedirectResponse(
                "/?msg=Successfully-Registered", status_code=status.HTTP_302_FOUND
            )  # default is post request, to use get request added status code 302
        except IntegrityError:
            form.__dict__.get("errors").append("Duplicate username or email")
            return templates.TemplateResponse("users/register.html", form.__dict__)
    return templates.TemplateResponse("users/register.html", form.__dict__)

Lets understand whats happening:

  • Our users will visit a link e.g. http://127.0.0.1:8000/register/ and they should see a form like this
  • After they fill-up the form, they would click on the submit button and will make a post request that will contain the details entered by the users.
  • Why post request? Because if they submit the form with get request all the details even password would be appended to the URL and this makes it extremely insecure.
  • When the post request would come we will load data in class attributes and will verify if the data is valid or not.

Lets implement a class which would accept and validate the form data. Lets create a new file webapps > users > forms.py and type the below code in it. Sorry, I made the code, an image, so that you have to type and will better understand it.

If you really typed it, you should be understanding it already. I know legends would have gone to the git commit URL at the bottom of the post and would have copied it from there. This is actually a line from my mentor from my fresher's days. He used to say "My job as a mentor is just that you understand code and don't copy" 😇
Moving on to the next section, We need to tell our fastapi app that we have made this functionality. So, its time to modify webapps > base.py

from fastapi import APIRouter
from webapps.jobs import route_jobs
from webapps.users import route_users  #new


api_router = APIRouter()
api_router.include_router(route_jobs.router, prefix="", tags=["job-webapp"])
api_router.include_router(route_users.router, prefix="", tags=["users-webapp"])  #new

The final touch, We need to make a HTML form which should display a form as well as should display the errors in the form. Plus, any data which the user fills in should be there is form has error. Thats why we have value = {{username}} kind of thing. Lets create a template at templates > users > register.html

{% extends "shared/base.html" %}


{% block title %}
  <title>Job Detail</title>
{% endblock %}

{% block content %}
  <div class="container">
    <div class="row">
      <h5 class="display-5">Signup to JobBoard</h5>
      <div class="text-danger font-weight-bold">
          {% for error in errors %}
            <li>{{error}}</li>
          {% endfor %}
      </div>
    </div>

    <div class="row my-5">
      <form method="POST">
        <div class="mb-3">
          <label>Username</label>
          <input type="text" required class="form-control" name="username" value="{{username}}" placeholder="username">
        </div>
        <div class="mb-3">
          <label>Email</label>
          <input type="text" required placeholder="Your email" name="email" value="{{email}}" class="form-control">
        </div>
        <div class="mb-3">
          <label>Password</label>
          <input type="password" required placeholder="Choose a secure password" value="{{password}}" name="password" class="form-control">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
    </div>
  </div>
  {% endblock %}

Woahhh!  That was a lot of work. Time to test our work now. visit http://127.0.0.1:8000/register/ and you should see the form and it should be working properly. There is a huge scope of improvement,especially in this code, We should have done much more validation in the implementation of form, which I am leaving up to you. We should have also checked if a user with same email/username already exists?

One last thing, promise this is last 😁, Once someone successfully registers, we need to show them a msg that "Yes,you were registered" For that I am redirecting the user to homepage with a query parameter.responses.RedirectResponse("/?msg=Successfully-Registered"...) We can send the query parameter in the templates and show a proper message to the user. For this to happen we have to modify our homepage logic a little. We have to accept msg as a query parameter and pass it to the template. In the file webapps > jobs > route_jobs.py we need to change home function:

@router.get("/")
async def home(request: Request, db: Session = Depends(get_db),msg:str = None):   #new
    jobs = list_jobs(db=db)
    return templates.TemplateResponse(
        "general_pages/homepage.html", {"request": request, "jobs": jobs,"msg":msg}   #new
    )

Similarly, in the templates > general_pages > homepage.html we need to accept and pass it to a component that would render a beautiful bootstrap alert.

{% extends "shared/base.html" %}


{% block title %}
  <title>Job Board</title>
{% endblock %}

{% block content %}

<!-- New with statement to show alert -->
  {% with msg=msg %}    
    {% include "components/alerts.html" %}
  {% endwith %}

The final thing, we have to design our alert component in templates > components > alerts.html

{% if msg %}
  <div class="alert alert-info" role="alert">
    {{msg}}
  </div>
{% endif %}

Time to test our implementation:

Final git commit: https://github.com/nofoobar/JobBoard-Fastapi/commit/98a353c7d87ffd8e0fe0b2d5adfd7effd1608100
 

Prev: 26 : Job … Next: 28 : Securing …