[PYTHON] Let's make a nervous breakdown application with Vue.js and Django-Rest-Framework [Part 5] ~ User authentication ~

<< Part 4 | Part 6 >>

Custom user creation

When starting a new project, it is highly recommended to create a custom user model, even if the default User is sufficient.

This is officially the case, so create a custom user.

users application creation

$ django-admin startapp users

Add the created application to settings.py

settings.py


.
..
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'users', #Postscript
]
...
..
.

User model creation

users/models.py


from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager
from django.utils import timezone

# Create your models here.
class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField('username', max_length=150, unique=True)
    first_name = models.CharField('first name', max_length=30, blank=True)
    last_name = models.CharField('last name', max_length=150, blank=True)
    email = models.EmailField('email address', blank=True)
    is_staff = models.BooleanField('is_staff', default=False)
    is_active = models.BooleanField('is_active', default=True)
    date_joined = models.DateTimeField('date joined', default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    class Meta:
        verbose_name = 'user'
        verbose_name_plural = 'users'

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

UserManager class creation

users/user_manager.py



from django.contrib.auth.base_user import BaseUserManager

class UserManager(BaseUserManager):
    use_in_migrations = True

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

    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', 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(username, email, password, **extra_fields)

Edit the created User model

users/models.py


.
..
...
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin #Delete UserManager
from users.user_manager import UserManager #Import your own UserManager class
...
..
.

Added to settings.py so that custom user model is used as authentication model

settings.py


.
..
...
AUTH_USER_MODEL = 'users.User'
...
..
.

DB migration

$ python3 manage.py makemigrations
Migrations for 'users':
  users/migrations/0001_initial.py
    - Create model User

There is an error in Migrate ... It's a story of creating a custom user from the beginning and migrating DB ... crying

$ python3 manage.py migrate
$ python3 manage.py migrate
Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
...
..
.
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency users.0001_initial on database 'default'.

Edit settings.py and urls.py

settings.py


.
..
...
INSTALLED_APPS = [
    # 'django.contrib.admin',Comment out
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'users',
]
...
..
.

config/urls.py


.
..
...
urlpatterns = [
    # path('admin/', admin.site.urls), #Comment out
    url('api/', include(ROUTER.urls)),
]

My Great again

$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: auth, contenttypes, sessions, users
Running migrations:
  Applying users.0001_initial... OK

Uncomment settings.py and urls.py and make sure DB migration does not result in an error.

$ python3 manage.py makemigrations
No changes detected

$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
  No migrations to apply.

solved. (Please create superuser.)

Let's create an API using User.

Create serializers.py directly under the users directory

users/serializers.py


from rest_framework.serializers import ModelSerializer
from users.models import User

class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

Create View

users/views.py


from rest_framework.viewsets import ModelViewSet
from users.models import User
from users.serializers import UserSerializer

# Create your views here.
class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

Edit urls.py

config/urls.py


"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url
from rest_framework import routers
from users.views import UserViewSet #add to

ROUTER = routers.DefaultRouter()
ROUTER.register('users', UserViewSet) #add to


urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/', include(ROUTER.urls)),
]

Access http://127.0.0.1:8000/api/users/.

image.png

If you create a user by POST from here, the password will be created in plain text. This is a terrible situation.

Edit serializers.py so that the password is hashed.

users/serializers.py


from rest_framework.serializers import ModelSerializer
from users.models import User
from django.contrib.auth.hashers import make_password #add to

class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

    #Override create method
    def create(self, validated_data):
        password = validated_data.get('password', None)
        if password is not None:
            validated_data['password'] = make_password(password)
        return super().create(validated_data);

Installation and configuration of authentication library (JWT)

This time we will use the JWT method. Set as officially.

pip3 install djangorestframework-jwt

settings.py


.
..
...
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

config/urls.py


"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url
from rest_framework import routers
from users.views import UserViewSet
from rest_framework_jwt.views import obtain_jwt_token #add to

ROUTER = routers.DefaultRouter()
ROUTER.register('users', UserViewSet)


urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/', include(ROUTER.urls)),
    url('api-token-auth/', obtain_jwt_token), #add to
]

When I access http://127.0.0.1:8000/api/users/, the result is no longer displayed. image.png

Get token with curl

curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}' http://127.0.0.1:8000/api-token-auth/

Acquisition result

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTgwOTIwNDcwLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvLmpwIn0.O8h4Js07Nr3aILHZyoAYlPklSGX-TJZs6k6tpB4xd0Y"}(concentration) tabatadikinoMBP:concentratio tabatadaiki$ curl -X "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTgwOTIwMzA5LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvLmpwIn0.vvKtzWk6d0qhDpwy3PgyiZ6ovkw-2JHJyn7mf25XrsU"

Get users by specifying the obtained token

curl -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTgwOTIwMzA5LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvLmpwIn0.vvKtzWk6d0qhDpwy3PgyiZ6ovkw-2JHJyn7mf25XrsU" http://127.0.0.1:8000/api/users/

Acquisition result

[{"id":1,"password":"pbkdf2_sha256$150000$lLUvsL7bxcSs$/PhUu6BtJVaQtuhfkI6rj7frwvLHbpVFjFoCY7S8+0w=","last_login":null,"is_superuser":true,"username":"admin","first_name":"","last_name":"","email":"[email protected]","is_staff":true,"is_active":true,"date_joined":"2020-02-04T15:23:57.201798Z","groups":[],"user_permissions":[]},{"id":2,"password":"yktgy780","last_login":null,"is_superuser":true,"username":"tabatadaiki","first_name":"tabata","last_name":"daiki","email":"[email protected]","is_staff":true,"is_active":true,"date_joined":"2020-02-05T15:21:26.470920Z","groups":[],"user_permissions":[]},{"id":3,"password":"akasatana","last_login":null,"is_superuser":true,"username":"a","first_name":"a","last_name":"a","email":"","is_staff":true,"is_active":true,"date_joined":"2020-02-05T15:27:12.682966Z","groups":[],"user_permissions":[]},{"id":5,"password":"pbkdf2_sha256$150000$v9YkaBjzyGXP$VyQEp/yk6gWf8FEvI7C9TxCBANgXi9JxivQ/wjkjjpg=","last_login":null,"is_superuser":true,"username":"tabata","first_name":"tabata","last_name":"tabata","email":"","is_staff":true,"is_active":true,"date_joined":"2020-02-05T15:41:50.518882Z","groups":[],"user_permissions":[]}]

It looks like I was able to authenticate.

Recommended Posts

Let's make a nervous breakdown application with Vue.js and Django-Rest-Framework [Part 6] ~ User Authentication 2 ~
Let's make a nervous breakdown application with Vue.js and Django-Rest-Framework [Part 5] ~ User authentication ~
Let's make a nervous breakdown app with Vue.js and Django-Rest-Framework [Part 2] ~ Vue setup ~
Let's make a nervous breakdown app with Vue.js and Django-Rest-Framework [Part 1] ~ Django setup ~
Try to make a nervous breakdown application with Vue.js and Django-Rest-Framework [Part 4] ~ MySQL construction and DB migration with Docker ~
Let's make a WEB application for phone book with flask Part 2
Let's make a WEB application for phone book with flask Part 3
Let's make a WEB application for phone book with flask Part 4
Let's make a simple game with Python 3 and iPhone
Let's make a Mac app with Tkinter and py2app
Let's make a websocket client with Python. (Access token authentication)
Let's make a GUI with python.
Let's make a breakout with wxPython
Let's make a graph with python! !!
Let's make a supercomputer with xCAT
Make a thermometer with Raspberry Pi and make it viewable with a browser Part 4
Let's make a shiritori game with Python
Let's make a voice slowly with Python
Let's make a simple language with PLY 1
Let's make a web framework with Python! (1)
Let's make a tic-tac-toe AI with Pylearn 2
Let's make a Twitter Bot with Python!
Let's make a web framework with Python! (2)
Make a thermometer with Raspberry Pi and make it visible on the browser Part 3
Let's make an A to B conversion web application with Flask! From scratch ...
Try creating a web application with Vue.js and Django (Mac)-(1) Environment construction, application creation
Let's replace UWSC with Python (5) Let's make a Robot
Let's make an app that can search similar images with Python and Flask Part1
Let's make an app that can search similar images with Python and Flask Part2
Let's make a Makefile and build it (super beginner)
[Let's play with Python] Make a household account book
Let's make dependency management with pip a little easier
[Super easy] Let's make a LINE BOT with Python.
Easily create authentication, user management, and multilingual systems with Flask-AppBuilder
Make a tky2jgd plugin with no practicality in QGIS Part 2
Associate Python Enum with a function and make it Callable
Make a tky2jgd plugin with no practicality in QGIS Part 1
Make a 2D RPG with Ren'Py (3) -Items and Tool Shop
Let's make a diagram that can be clicked with IPython
Make a BLE thermometer and get the temperature with Pythonista3