We have created the project skeletal structure in the previous article, this article will build on it. it will cover
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
Check previous articles if interested!
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 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:
We have also added multiple fields for this project user which are
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.
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.
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)
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(...)
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.
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
we can see our schema in the error message ?, cool, let's try registering a new user, I'll call it "test5@gmail.com"
looking great, now let's test the serializer validation error, we will try to register the same user again
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
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
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!