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 sans une ou plusieurs API-Il semble que l'interface utilisateur ne puisse pas être ouverte, alors enregistrez l'API de connexion pour le moment
]
#Tout ajouter ci-dessous
if settings.DEBUG: # DEBUG=Utilisé uniquement lorsque True (pendant le développement)
from rest_framework.schemas import get_schema_view
from rest_framework_swagger import renderers
schema_view = get_schema_view(
title='Liste des API',
public=True,
renderer_classes=[renderers.OpenAPIRenderer, renderers.SwaggerUIRenderer])
urlpatterns += [
url(API_ROOT + 'api-auth/', include('rest_framework.urls')),
url('swagger-ui/', schema_view),
]
Accédez à http://0.0.0.0:8000/swagger-ui/ et lorsque l'écran suivant apparaît, Log in
en haut à droite Appuyez pour vous connecter
Vous pouvez utiliser les ModelViewSets
que tout le monde aime.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import ModelViewSet #cette
class UserViewSets(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Terminé (en utilisant swagger-ui)
Si vous souhaitez augmenter le nombre de méthodes de requête ultérieurement, ajoutez les mixins correspondants.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import GenericViewSet #cette
from rest_framework.mixins import CreateModelMixin #cette
class UserViewSets(GenericViewSet, CreateModelMixin):
queryset = User.objects.all()
serializer_class = UserSerializer
Si vous souhaitez augmenter le nombre de méthodes de requête ultérieurement, vous pouvez ajouter l'APIView correspondant.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.generics import CreateAPIView #cette
class UserViewSets(CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Pour les génériques, la méthode de spécification de urls.py est différente
backend/src/config/urls.py
# =========== viewsets ===========
router = DefaultRouter()
router.register('user', user_views.UserViewSets) #Dans le cas des vues, le routeur peut être utilisé
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()), #quant aux modèles d'URL_views()Ajouter avec
]
Le résultat est le même que la méthode 1
Pour une personne aussi égoïste, c'est http_method_names
.
Je me demande s'il sera utilisé dans des situations où je l'ai implémenté mais que je ne souhaite pas encore le publier.
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'] #La méthode de requête HTTP est en minuscule (important), pas en majuscule
Spécifions ʻIsAuthenticated` pour permission_class.
Tout d'abord, modifiez settings.py.
backend/src/config/settings/settings.py
.
..
...
REST_FRAMEWORK = {
#ajouter à
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
# 'common.permissions.IsSuperuser', #Si vous avez créé votre propre autorisation, ajoutez-la ici.
),
...
..
.
}
Si vous demandez l'API dans l'état non authentifié, «403» sera renvoyé.
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.permissions import IsAuthenticated #ajouter à
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
permission_classes = [IsAuthenticated,] #ajouter à
queryset = User.objects.all()
serializer_class = UserSerializer
Dans un tel cas, c'est un décorateur «@ action».
(Si la version DRF est ancienne, il s'agit du décorateur @ list_route
ou @ detail_route
. Il semble qu'il ait été intégré dans le décorateur @ action
.)
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.decorators import action #ajouter à
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):
"""Renvoie les informations de connexion de l'utilisateur
"""
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):
"""Renvoie les informations de connexion de l'utilisateur
"""
#Le processus est defail=Identique à False. Je suis désolé(. _ .)
return Response(
status=status.HTTP_200_OK,
data=UserSerializer(self.request.user).data)
Ce sera une API telle que ʻapi_root / {pk} / url_name / `.
Une personne aussi égoïste est le décorateur «@ action» «chemin_url».
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):
"""Renvoie les informations de connexion de l'utilisateur
"""
return Response(
status=status.HTTP_200_OK,
data=UserSerializer(self.request.user).data)
Il peut être recommandé pour ceux qui n'aiment pas l'URL générée par detail = True
lol
Installer django-filter avec pip
pip3 install django-filter==2.2.0
Créez filters.py (le nom du fichier est arbitraire) et définissez des filtres
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__'
#Les champs que vous ne souhaitez pas spécifier dans le paramètre de requête doivent être ajoutés pour être exclus.
# exclude = [
# 'password',
# 'date_created',
# 'date_updated',
# ]
Utiliser le filtre défini dans les vues
backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from django_filters.rest_framework import DjangoFilterBackend #ajouter à
from api.users.filters import UserSearchFilter #ajouter à
from rest_framework.viewsets import ModelViewSet
class UserViewSets(ModelViewSet):
filter_backends = [DjangoFilterBackend,] #ajouter à
filter_class = UserSearchFilter #ajouter à
queryset = User.objects.all()
serializer_class = UserSerializer
Vous pourrez GET en spécifiant les paramètres de requête.
backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User
class UserSerializer(ModelSerializer):
"""Sérialiseur utilisateur
"""
class Meta:
model = User
fields = '__all__'
Je ne veux pas lancer de groupes ou de user_permissions comme paramètres
Dans un tel cas, spécifiez ʻextra_kwargs`.
backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User
class UserSerializer(ModelSerializer):
"""Sérialiseur utilisateur
"""
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},
}
Je l'ai pressé en toute sécurité ↓
Comme il est uniquement en lecture seule (lecture seule), il inclura celui spécifié dans ʻextra_kwargs` au moment de l'acquisition (GET).
Par exemple, la valeur de la destination de la relation contient uniquement la clé primaire.
école
Dans un tel cas, vous pouvez utiliser SerializerMethodField
.
Après avoir défini les champs, nous préparerons une fonction pour get_field name
.
backend/src/api/users/serializers.py
import json #ajouter à
from django.core.serializers import serialize #ajouter à
from rest_framework.serializers import ModelSerializer, SerializerMethodField #ajouter à
from common.models import User
class UserSerializer(ModelSerializer):
"""Sérialiseur utilisateur
"""
school = SerializerMethodField() #Ajouter le champ
# get_Ajout d'une fonction pour le nom du champ
def get_school(self, obj):
"""Renvoie un objet sérialisé JSON de l'objet école
Args:
obj (User):Objet utilisateur
Returns:
dict:Un objet qui sérialise l'objet User au format JSON
"""
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:
...
L'objet école a été étendu à école
, qui était la clé principale auparavant (. _.)
SerializerMethodField est pratique car il peut renvoyer n'importe quelle valeur ensemble.
Un tel égoïsme est une option de «profondeur».
backend/src/api/users/serializers.py
from common.models import User
class UserSerializer(ModelSerializer):
"""Sérialiseur utilisateur
"""
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 #La valeur par défaut est 0, il est donc inutile de spécifier 0
Si vous faites depth = 2
, cela obtiendra encore plus de relations. (Dans cet exemple, si l'école
a plus de relations à venir, elle renverra également cet objet)
SerializerMethodField
est recommandé après tout car il sera gênant lors de la nouvelle inscription avec POST. Il peut être efficace lorsqu'il est utilisé uniquement dans GET.Par défaut, il ne renvoie que «token».
Si vous souhaitez inclure ici les informations utilisateur en plus du jeton, créez votre propre jwt_response_payload_handler
.
Créez jwt_utils.py (le nom du fichier est arbitraire)
backend/src/common/jwt_utils.py
def jwt_response_payload_handler(token, user=None, request=None):
"""Réponse personnalisée pour l'authentification JWT
"""
return {
'token': token,
'first_login': user.first_login,
'id': user.id,
}
Modifiez settings.py.
backend/src/config/settings/settings.py
.
..
...
# JWT_Ajouter AUTH
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'common.jwt_utils.jwt_response_payload_handler', # JWT_RESPONSE_PAYLOAD_Jwt fait maison pour MANIPULATEUR_response_payload_Spécifier le gestionnaire
}
J'ai pu augmenter la réponse (. _.)
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
La structure des répertoires est la suivante
project_root #Racine du projet
└── backend
└── src
cd project_root/backend/src
django-admin startproject confing .
La structure des répertoires est la suivante (les fichiers de configuration peuvent être collectés dans config)
project_root #Racine du projet
└── backend
└── src
├── config
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│
└── manage.py
project_root #Racine du projet
└── backend
└── src
├── config
│ ├── __init__.py
│ ├── settings
│ │ ├── settings.py
│ │ ├── development.py #Pour le developpement
│ │ └── production.py #Pour la production
│ │
│ ├── urls.py
│ └── wsgi.py
└── manage.py
Modifier settings.py
(Correction de BASE_DIR pour être le répertoire src
)
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
...
..
.
Correction de 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') #Lire le développement
...
..
.
Modifiez wsgi.py pour le déploiement
backend/src/config/wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings')
│
↓
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings.production')
Développement fixe.py
backend/src/config/settings/development.py
from confing.settings.settings import *
ALLOWED_HOSTS = ['*']
Modifier production.py
backend/src/config/settings/development.py
from confing.settings.settings import *
Modifier 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
]
Préparez docker-compose.yml et Dockerfile
project_root #Racine du projet
├── backend
│ ├── src
│ │ ├── config
│ │ │ ├── __init__.py
│ │ │ ├── settings
│ │ │ │ ├── settings.py
│ │ │ │ ├── development.py
│ │ │ │ └── production.py
│ │ │ │
│ │ │ ├── urls.py
│ │ │ └── wsgi.py
│ │ |
│ │ └── manage.py
│ │
│ └── Dockerfile #ajouter à
│
└── docker-compose.yml #ajouter à
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
Démarrer le conteneur Docker
docker-compose up -d
docker-compose stop
et ensuite docker-compose up -d
. Si la correspondance est délicate, veuillez envisager d'introduire «wait-for-it.sh».Succès si cet écran apparaît
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() #Lire les informations de connexion à la base de données (.env.BASE DE DONNÉES de développement_Lira l'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):
"""Modèle de base
"""
date_created = models.DateTimeField('Date et heure de création', default=timezone.now)
date_updated = models.DateTimeField('Dernière modification', auto_now_add=True)
date_deleted = models.DateTimeField('Supprimer la date et l'heure', null=True)
class Meta:
abstract = True #← Obligatoire
django-admin startapp common
INSTALLED_APPS = [
.
..
...
'base',
'common',
]
Créer 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)
Le fonctionnaire recommande de mettre en œuvre un modèle d'utilisateur personnalisé, alors suivez-le (. _.)
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('adresse mail', blank=True, null=True)
username = models.CharField('Nom d'utilisateur', max_length=150, unique=True)
display_name = models.CharField('Nom d'affichage de l'écran', max_length=30, blank=True, null=True)
last_name = models.CharField('Nom de famille', max_length=150, blank=True, null=True)
first_name = models.CharField('Nom', max_length=30, blank=True, null=True)
is_staff = models.BooleanField('Drapeau du personnel', default=False)
is_active = models.BooleanField('Drapeau valide', default=True)
first_login = models.BooleanField('Première connexion', 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' #Spécifiez un modèle utilisateur personnalisé pour le modèle utilisateur utilisé pour l'authentification.
Si vous oubliez de spécifier ʻAUTH_USER_MODEL`, vous souffrirez de l'erreur suivante.
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