module | version |
---|---|
Python | 3.7.7 |
django-environ | 0.4.5 |
Django | 2.2.11 |
djangorestframework | 3.11.0 |
djangorestframework-jwt | 1.11.0 |
django-rest-swagger | 2.2.0 |
django-filter | 2.2.0 |
mysqlclient | 1.4.6 |
pip3 install django-rest-swagger==2.2.0
backend/src/config/settings/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',
)
}
backend/src/config/settings/developement.py
.
..
...
INSTALLED_APPS += [
'rest_framework_swagger',
]
REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'rest_framework.schemas.coreapi.AutoSchema'
backend/src/config/urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings #Postscript
from rest_framework_jwt.views import obtain_jwt_token #Postscript
urlpatterns = [
path('admin/', admin.site.urls),
url('api/v1/login/', obtain_jwt_token), #Swagger without one or more APIs-It seems that the UI cannot be opened, so register the login API for the time being
]
#Add all below
if settings.DEBUG: # DEBUG=Used only when True (during development)
from rest_framework.schemas import get_schema_view
from rest_framework_swagger import renderers
schema_view = get_schema_view(
title='API list',
public=True,
renderer_classes=[renderers.OpenAPIRenderer, renderers.SwaggerUIRenderer])
urlpatterns += [
url(API_ROOT + 'api-auth/', include('rest_framework.urls')),
url('swagger-ui/', schema_view),
]
If you access http://0.0.0.0:8000/swagger-ui/ and the following screen appears, the upper right Log in
Press to log in
You can use the ModelViewSets
that everyone loves.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import ModelViewSet #this
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Finished (using swagger-ui)
If you want to increase the number of request methods later, you can add the corresponding mixins.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import GenericViewSet #this
from rest_framework.mixins import CreateModelMixin #this
class UserViewSets(GenericViewSet, CreateModelMixin):
queryset = User.objects.all()
serializer_class = UserSerializer
If you want to increase the number of request methods later, you can add the corresponding APIView.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.generics import CreateAPIView #this
class UserViewSets(CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
For generics, the method of specifying urls.py is different
backend/src/config/urls.py
# =========== viewsets ===========
router = DefaultRouter()
router.register('user', user_views.UserViewSets) #In the case of viewsets, router can be used
urlpatterns = [
path('admin/', admin.site.urls),
url('api/v1/login/', obtain_jwt_token),
url('', include(router.urls)),
]
backend/src/config/urls.py
# =========== generics ===========
urlpatterns = [
path('admin/', admin.site.urls),
url('api/v1/login/', obtain_jwt_token),
url('user/', user_views.UserViewSets.as_view()), #as to url patterns_views()Add with
]
The result is the same as method 1
For such selfishness, it's http_method_names
.
I wonder if it will be used in situations where I have implemented it but do not want to publish it yet.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ['get', 'post'] #HTTP request method is lowercase (important), not uppercase
Let's specify ʻIsAuthenticated` for permission_class.
First, modify settings.py.
backend/src/config/settings/settings.py
.
..
...
REST_FRAMEWORK = {
#add to
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
# 'common.permissions.IsSuperuser', #If you created your own permission, add it here.
),
...
..
.
}
If you request the API in the unauthenticated state, 403
will be returned.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.permissions import IsAuthenticated #add to
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
permission_classes = [IsAuthenticated,] #add to
queryset = User.objects.all()
serializer_class = UserSerializer
At that time, it's a @ action
decorator.
(If the DRF version is older, this is the @ list_route
or @ detail_route
decorator. It looks like it's integrated into the @ action
decorator.)
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.decorators import action #add to
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=False, methods=['get'], url_path='get_user', url_name='get_user')
def get_user(self, request, *kwargs):
"""Returns login user information
"""
return UserSerializer(self.request.user).data
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=True, methods=['get'], url_path='get_user', url_name='get_user')
def get_user(self, request, pk=None, **kwargs):
"""Returns login user information
"""
#The process is defail=Same as False. I'm sorry(. _ .)
return Response(
status=status.HTTP_200_OK,
data=UserSerializer(self.request.user).data)
It will be an API such as ʻapi_root / {pk} / url_name / `.
Such a selfish person is @ action
decorator ʻurl_path`.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=False, methods=['get'], url_path='get_user/(?P<user_id>[0-9]+)', url_name='get_user')
def get_user(self, request, user_id=None):
"""Returns login user information
"""
return Response(
status=status.HTTP_200_OK,
data=UserSerializer(self.request.user).data)
It may be recommended for those who do not like the URL generated by detail = True
lol
pip install django-filter
pip3 install django-filter==2.2.0
Create filters.py (file name is arbitrary) and define filters
backend/src/api/users/filters.py
from common.models import User
from django_filters import FilterSet
class UserSearchFilter(FilterSet):
class Meta:
model = User
fields = '__all__'
#Fields that you do not want to be specified in the query parameter should be added to exclude.
# exclude = [
# 'password',
# 'date_created',
# 'date_updated',
# ]
Use the defined filter in views
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from django_filters.rest_framework import DjangoFilterBackend #add to
from api.users.filters import UserSearchFilter #add to
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
filter_backends = [DjangoFilterBackend,] #add to
filter_class = UserSearchFilter #add to
queryset = User.objects.all()
serializer_class = UserSerializer
You will be able to get by specifying the query parameters.
backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User
class UserSerializer(ModelSerializer):
"""User serializer
"""
class Meta:
model = User
fields = '__all__'
I don't want to throw groups or user_permissions as parameters
In such a case, specify ʻextra_kwargs`.
backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User
class UserSerializer(ModelSerializer):
"""User serializer
"""
class Meta:
model = User
fields = '__all__'
extra_kwargs = {
'is_superuser': {'read_only': True},
'date_created': {'read_only': True},
'date_deleted': {'read_only': True},
'is_staff': {'read_only': True},
'is_active': {'read_only': True},
'groups': {'read_only': True},
'user_permissions': {'read_only': True},
}
I squeezed it safely ↓
Since it is only read-only (read_only), it will include the one specified in ʻextra_kwargs` at the time of acquisition (GET).
For example, the value of the relation destination contains only the primary key.
school
In such a case, you can use SerializerMethodField
.
After defining the field, we will prepare a function for get_field name
.
backend/src/api/users/serializers.py
import json #add to
from django.core.serializers import serialize #add to
from rest_framework.serializers import ModelSerializer, SerializerMethodField #add to
from common.models import User
class UserSerializer(ModelSerializer):
"""User serializer
"""
school = SerializerMethodField() #Add field
# get_Added function for field name
def get_school(self, obj):
"""Returns a JSON serialized object of the school object
Args:
obj (User):User object
Returns:
dict:An object that serializes the User object into JSON format
"""
if obj.school is not None:
data = serialize('json', [obj.school,])
objs = json.loads(data)
return json.dumps(objs[0]['fields'])
return None
class Meta:
...
The school object was expanded to school
, which was the primary key earlier (. _.)
SerializerMethodField is convenient because you can return any value together.
Such selfishness is a depth
option.
backend/src/api/users/serializers.py
from common.models import User
class UserSerializer(ModelSerializer):
"""User serializer
"""
class Meta:
model = User
fields = '__all__'
extra_kwargs = {
'is_superuser': {'read_only': True},
'date_created': {'read_only': True},
'date_deleted': {'read_only': True},
'is_staff': {'read_only': True},
'is_active': {'read_only': True},
'groups': {'read_only': True},
'user_permissions': {'read_only': True},
}
depth = 1 #The default is 0, so it doesn't make sense to specify 0
If you do depth = 2
, it will get even further relations. (In this example, if school
has more relations ahead, it will also return that object)
SerializerMethodField
is recommended after all because it will be troublesome when newly registering with POST. It may be effective when it is used only in GET.By default, it only returns token
.
If you want to include user information in addition to token here, create your own jwt_response_payload_handler
.
Create jwt_utils.py (file name is arbitrary)
backend/src/common/jwt_utils.py
def jwt_response_payload_handler(token, user=None, request=None):
"""JWT authentication custom response
"""
return {
'token': token,
'first_login': user.first_login,
'id': user.id,
}
Modify settings.py.
backend/src/config/settings/settings.py
.
..
...
# JWT_Add AUTH
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'common.jwt_utils.jwt_response_payload_handler', # JWT_RESPONSE_PAYLOAD_HANDLER self-made jwt_response_payload_Specify handler
}
I was able to increase the response (. _.)
pip3 install django-environ==0.4.5 Django==2.2.11 djangorestframework==3.11.0 mysqlclient==1.4.6
mkdir -p project_root/backend/src
The directory structure is as follows
project_root #Project root
└── backend
└── src
cd project_root/backend/src
django-admin startproject confing .
The directory structure is as follows (configuration files can be collected in config)
project_root #Project root
└── backend
└── src
├── config
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│
└── manage.py
project_root #Project root
└── backend
└── src
├── config
│ ├── __init__.py
│ ├── settings
│ │ ├── settings.py
│ │ ├── development.py #For development
│ │ └── production.py #For production
│ │
│ ├── urls.py
│ └── wsgi.py
└── manage.py
Modify settings.py
(Fixed BASE_DIR to be the src
directory)
backend/src/config/settings/settings.py
.
..
...
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # >> /src/confing/settings
│
↓
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # >> /src
...
..
.
Fix manage.py
backend/src/manage.py
.
..
...
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings')
│
↓
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings.development') #Read development
...
..
.
Modify wsgi.py for deployment
backend/src/config/wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings')
│
↓
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings.production')
Fixed development.py
backend/src/config/settings/development.py
from confing.settings.settings import *
ALLOWED_HOSTS = ['*']
Modify production.py
backend/src/config/settings/development.py
from confing.settings.settings import *
Modify settings.py
backend/src/config/settings.settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', #Postscript
]
Prepare docker-compose.yml and Dockerfile
project_root #Project root
├── backend
│ ├── src
│ │ ├── config
│ │ │ ├── __init__.py
│ │ │ ├── settings
│ │ │ │ ├── settings.py
│ │ │ │ ├── development.py
│ │ │ │ └── production.py
│ │ │ │
│ │ │ ├── urls.py
│ │ │ └── wsgi.py
│ │ |
│ │ └── manage.py
│ │
│ └── Dockerfile #add to
│
└── docker-compose.yml #add to
docker-compose.yml
version: '3'
services:
web:
build:
context: ./
dockerfile: ./backend/Dockerfile
container_name: drf_web
volumes:
- './backend/src:/src'
environment:
- LC_ALL=ja_JP.UTF-8
ports:
- '8000:8000'
depends_on:
- db
command: python3 manage.py runserver 0.0.0.0:8000
restart: always
tty: true
db:
image: mariadb:latest
container_name: drf_db
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
environment:
- MYSQL_ROOT_USER=root
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=db_drf
- MYSQL_USER=user
- MYSQL_PASSWORD=user
volumes:
- db_data:/var/lib/mysql
ports:
- '3306:3306'
volumes:
db_data:
driver: local
FROM ubuntu:18.04
RUN apt update && apt install -y locales python3-pip python3.7 python3-dev libssl-dev libffi-dev libgeos-dev libmysqlclient-dev
RUN apt-get update && apt-get install -y mysql-client python3-gdal
RUN mkdir /src \
&& rm -rf /var/lib/apt/lists/* \
&& echo "ja_JP UTF-8" > /etc/locale.gen \
&& locale-gen
WORKDIR /src
ADD ./backend/src /src/
RUN LC_ALL=ja_JP.UTF-8 pip3 install -r requirements.txt
Start Docker container
docker-compose up -d
docker-compose stop
and then docker-compose up -d
. If the correspondence is delicate, please consider introducing wait-for-it.sh
.Success if this screen appears
cd backend/src/
touch .env.development
backend/src/.env.development
DEBUG=True
DATABASE_URL=mysql://user:user@db:3306/db_drf
backend/src/config/settings/development.py
import environ
ENV_FILE = os.path.join(BASE_DIR, '.env.development')
ENV = environ.Env()
ENV.read_env(ENV_FILE)
DEBUG = ENV.get_value('DEBUG', cast=bool)
DATABASES['default'] = ENV.db() #Read DB connection information (.env.development DATABASE_Reads the URL)
...
..
.
django-admin startapp base
INSTALLED_APPS = [
.
..
...
'base',
]
backend/src/base/models.py
from django.db import models
from django.utils import timezone
# Create your models here.
from django.db import models
from django.utils import timezone
# Create your models here.
class BaseModel(models.Model):
"""Base model
"""
date_created = models.DateTimeField('Creation date and time', default=timezone.now)
date_updated = models.DateTimeField('Last Modified', auto_now_add=True)
date_deleted = models.DateTimeField('Delete date and time', null=True)
class Meta:
abstract = True #← Required
django-admin startapp common
INSTALLED_APPS = [
.
..
...
'base',
'common',
]
Create manager.py
cd backend/src/base
touch manager.py
backend/src/base/manager.py
from django.contrib.auth.models import UserManager
class UserManager(UserManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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', True)
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)
The official recommends implementing a custom user model, so follow it (. _.)
backend/src/common/models.py
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from base.manager import UserManager
from base.models import BaseModel
class User(AbstractBaseUser, PermissionsMixin, BaseModel):
email = models.EmailField('mail address', blank=True, null=True)
username = models.CharField('username', max_length=150, unique=True)
display_name = models.CharField('Screen display name', max_length=30, blank=True, null=True)
last_name = models.CharField('Surname', max_length=150, blank=True, null=True)
first_name = models.CharField('Name', max_length=30, blank=True, null=True)
is_staff = models.BooleanField('Staff flag', default=False)
is_active = models.BooleanField('Valid flag', default=True)
first_login = models.BooleanField('First login', default=True)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = verbose_name_plural = 'users'
db_table = 'user'
backend/src/config/settings/settings.py
.
..
...
AUTH_USER_MODEL = 'common.User' #Specify a custom user model for the user model used for authentication.
If you forget to specify ʻAUTH_USER_MODEL`, you will suffer from the following error.
python3 manage.py makemigrations
SystemCheckError: System check identified some issues:
ERRORS:
auth.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'User.groups'.
HINT: Add or change a related_name argument to the definition for 'User.groups' or 'User.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'User.user_permissions' or 'User.user_permissions'.
common.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'User.groups'.
HINT: Add or change a related_name argument to the definition for 'User.groups' or 'User.groups'.
common.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'User.user_permissions' or 'User.user_permissions'.
python3 manage.py makemigrations
python3 manage.py migrate
Recommended Posts