Home  >  Article  >  Backend Development  >  Django accounts management app ( registration and activation

Django accounts management app ( registration and activation

Patricia Arquette
Patricia ArquetteOriginal
2024-11-04 21:57:02971browse

What to expect from this article?

We have created the project skeletal structure in the previous article, this article will build on it. it will cover

  • Accounts database structure, including users and verification code.
  • Models serializers.
  • Account Views for account registration, activation. next article should cover the rest of the views, such as logging in, refreshing tokens, changing password, forgetting password and re-sending code.

I'll try to cover as many details as possible without boring you, but I still expect you to be familiar with some aspects of Python and Django.

the final version of the source code can be found on https://github.com/saad4software/alive-diary-backend

Series order

Check previous articles if interested!

  1. AI Project from Scratch, The Idea, Alive Diary
  2. Prove it is feasible with Google AI Studio
  3. Django API Project Setup
  4. Django accounts management app (1), registration and activation (You are here ?)

Accounts app setup

let's create serializers file in the app

from rest_framework import serializers
from app_account.models import *

app_account/serializers.py

and the urls file

from django.urls import path, include
from .views import *

urlpatterns = [

]

app_account/urls.py

finally, let's connect the app urls to the project urls by editing projects urls file as

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/account/', include('app_account.urls')),

]

alive_diary/urls.py

Now we can call any accounts url with the prefix "api/account/"

The Models

the main model for accounts app is the User model of course

from django.db import models
from django.contrib.auth.models import AbstractUser
from datetime import timedelta, datetime

class User(AbstractUser):
    userTypes = (
        ('A', 'Admin'),
        ('C', 'Client'),
    )

    role = models.CharField(max_length=1, choices=userTypes, default="C")

    hobbies = models.CharField(max_length=255, null=True, blank=True)
    job = models.CharField(max_length=100, null=True, blank=True)
    bio = models.TextField(null=True, blank=True)

    country_code = models.CharField(max_length=10, null=True, blank=True)
    expiration_date = models.DateTimeField(default=datetime.now()+timedelta(days=30))

app_account/models.py

Usually, it is better to keep the User model as simple as possible and move other details to a Profile model with a one-to-one relationship with the User, but to simplify things, I'll add the required user info directly to the User model this time.

We are inheriting from AbstractUser model, AbstractUser includes multiple fields

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(...)
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    email = models.EmailField(...)
    is_staff = models.BooleanField(...)
    is_active = models.BooleanField(...),
    date_joined = models.DateTimeField(...)

the most important ones are:

  • username will be used for logging in.
  • is_active will be used to prevent unverified accounts from logging in.
  • is_staff will distinguish admin (with value true) from normal users.

We have also added multiple fields for this project user which are

  • role to distinguish admin from client accounts, we can use is_staff for this simple project since we only have two roles, but a larger project can have more than 2 roles, making this field essential for permissions handling.
  • hobbies, job, bio Knowing more about the user can help build a better reflection, therefore we are asking for hobbies, job, and how the user describes him/her self.
  • country_code for statistics
  • expiration_date for subscription-based expiration date.

We also need a Verification code model to keep and track verification codes for account activation, forgetting passwords, and resending codes.

from rest_framework import serializers
from app_account.models import *

app_account/models.py

It connects with the User model and generates a random value of a 6-digit code number. it also has an expiration time of 24 hours. we have also email filed in case the user wants to validate multiple email addresses, it is rare and can be removed for this app. Let's move to serializers next.

The Registration API

let's start with the serializer

from django.urls import path, include
from .views import *

urlpatterns = [

]

app_account/serializers.py

We are using ModelSerializer, from Django rest framework. we selected the user model get_user_model() in class Meta and a list of serialized fields.

We have added two additional fields to the model serializer, password1, and password2. To validate they have the same value, we have overwritten the validate method. and to enforce using valid email as a username, we have added a field validator for username field.
the is_valid_email function should look somewhat like this

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/account/', include('app_account.urls')),

]

common/utils.py

Personally, I don't like regular expressions, I've never got the grasp of them, but they seem to be the best way to validate emails. If you got a better way, please share it with us.

Since our new fields password1 and password2 don't belong to the original user model, we removed them from the data dictionary and added the password field in order to use serializer data directly to create a new user.

Serializer's best practices dilemma

  • Should serializers use database queries and edit databases?
  • or should they only make basic field type validation (string, integer, ...)?

There is no clear answer actually, for instance, Django Rest framework model serializers seem to make queries for unique fields, like the serializer error we got when tried to create a use with the same name, it was generated by the serializer, not the view.
The Create, Save, Update methods writes values to the database.
Yet, accessing the database only in views seems to align more with Separation of Concerns and Flexibility.

What do you think is better?

I've read a lot about keeping things separated, even separating database queries from database updating methods. so let's try doing that. creating the AccountActivateView in the views.py file should look like.

In our case, we can overwrite the create method for RegisterSerializer in order to create a new user and validation code instants, and even sending the verification code from the serializer.

But instead, I'll keep the model related operations in the views file

Let's move to the registration view

from rest_framework import serializers
from app_account.models import *

app_account/views.py

We are using CreatAPIView from the rest framework, it accepts POST requests with the schema of serializer_class, the BrowsableAPIRenderer build a web interface for this API and the JSONRenderer is responsible for building the JSON response.

Overwriting the perform_create method allows us control the user creation mechanism, we are creating the user instant, making sure the is_active field is set to False, then creating the verification code instant that is connected to the new user model, and finally sending an email with the verification code to the user.

Sending the email requires the correct configuration for email fields in the setting file, please let me know if you have issues in this particular point to create a separate article for it

Finally, let's add the API url

from django.urls import path, include
from .views import *

urlpatterns = [

]

app_account/urls.py

Nice, let's try it out

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/account/', include('app_account.urls')),

]

opening http://localhost:8555/api/account/register, we should be able to see something like this (it is due to BrowsableAPIRenderer)

Django accounts management app ( registration and activation

the required fields are username, password1 and password2, we expect the email to be used as username.
it looks okay, it created a user model with a verification code model connected to it (used SqlBrowser to open the SQLite db file). But the default response looks like this with the status 201.

from django.db import models
from django.contrib.auth.models import AbstractUser
from datetime import timedelta, datetime

class User(AbstractUser):
    userTypes = (
        ('A', 'Admin'),
        ('C', 'Client'),
    )

    role = models.CharField(max_length=1, choices=userTypes, default="C")

    hobbies = models.CharField(max_length=255, null=True, blank=True)
    job = models.CharField(max_length=100, null=True, blank=True)
    bio = models.TextField(null=True, blank=True)

    country_code = models.CharField(max_length=10, null=True, blank=True)
    expiration_date = models.DateTimeField(default=datetime.now()+timedelta(days=30))

I Prefer all responses to have this schema

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(...)
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    email = models.EmailField(...)
    is_staff = models.BooleanField(...)
    is_active = models.BooleanField(...),
    date_joined = models.DateTimeField(...)

  • status should be either "success" or "error"
  • code is the response status code
  • data is the actual response data
  • message should contain error text or any other message

But how to do so?
The best way is by implementing a custom JSON renderer function. let's do it

import random

class VerificationCode(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    code = models.CharField(max_length=6, default=random.randint(111111, 999999))
    email = models.EmailField()
    expiration_date = models.DateTimeField(default=datetime.now()+timedelta(days=1))

    def __str__(self):
        return self.user.username

common/utils.py

We inherited from JSONRenderer and overwritten the render method. serializer errors.

  • started with reading the response status code, and putting it in the code field
  • and we moved the actual response data to the data field in our response schema
  • to distinguish errors from successful responses, we check the status code. If the status code is in the 200 family, it is a successful response
  • if not, it is an error. so we change the status to "error", and extract the error message from the response details field (if available).
  • serializer's validation errors don't have details fields, it is a dictionary indicating an error message (value) for each field name (key), so we created a small function dict2string to convert it to a simple string. I think it can be further improved, can you help with it?

that is it for the response schema, let's try using it now!
in views.py add our custom renderer to the register view class

from rest_framework import serializers
from app_account.models import *

app_account/views.py

running the server, and opening http://localhost:8555/api/account/register/ shows the difference directly

Django accounts management app ( registration and activation

we can see our schema in the error message ?, cool, let's try registering a new user, I'll call it "test5@gmail.com"

Django accounts management app ( registration and activation

looking great, now let's test the serializer validation error, we will try to register the same user again

Django accounts management app ( registration and activation

Wonderful, this is a validation error response, it was serialized as a field:message
what comes after registration? it is validation
register -> confirm email -> login -> whatever

The Activation API

We want to check if the user got the activation code we sent during registration or not, if the user sends the right code, we will activate their account, if not, we will ask them to check for it again, or maybe resend the code (another API for later)
Similar to the registration API-creating process, let's start with the serializer

from django.urls import path, include
from .views import *

urlpatterns = [

]

This is not related to a certain database model, so we are inheriting from the generic Serializer, notice that serializers are similar to forms, so we set the fields and their validation rules.
We are using two string fields (CharField), both are required, username which is the user email address, and code.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/account/', include('app_account.urls')),

]

app_account/views.py

Since we are using a custom API view, we are inheriting from APIView, it offers 5 functions (get, post, put, delete, and patch). We are de-serializing request data from POST requests, and validating its type, then making a query to find if the provided data exists or not, if it exists, we activate the user and remove the code object from its table. if not we send an error message saying it is an "invalid_code". finally, the URLs file should be updated to include this view's URL

from rest_framework import serializers
from app_account.models import *

app_account/urls.py

Now we can open the URL http://localhost:8555/api/account/activate/, we are using a custom API view, so it doesn't get the required field

Django accounts management app ( registration and activation

We can get the code from the database (for testing purposes). The request should look like

from django.urls import path, include
from .views import *

urlpatterns = [

]

If everything went successfully, the response should look like

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/account/', include('app_account.urls')),

]

That's it
Let's wrap it up! I know we haven't log in yet, but it became a very long article indeed, let's continue in the next article

Stay tuned ?

The above is the detailed content of Django accounts management app ( registration and activation. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn