用Docker+Django+Next+TypeScript+ECS创建应用的故事(第3部分)〜从创建Django模式到获取数据〜

首先

这是上一次的续集。

上一篇文章:利用Docker+Django+Next+TypeScript+ECS创建应用的经验分享(第2部分)- 从Django的初始配置到创建模型
下一篇文章:利用Docker+Django+Next+TypeScript+ECS创建应用的经验分享(第4部分)- Django测试篇

这次我在Django项目中创建了一个架构,并写到了在浏览器中获取数据的地方。

1. 创建架构

使用以下方式创建新文件夹和文件:

 myProject/
     app/
         app/
+           schema.py            
         ...
         api/
+           schema.py
+           utils/
+               validator.py           
    validator.py

创建用于用户创建和编辑个人资料的Mutation的验证方法。

import re
from graphql import GraphQLError

def validate_blank(value):
    if value == "":
        raise GraphQLError("Value is required")
    return value

def validate_too_long(value, num):
    if len(value) > num:
        raise GraphQLError("Value is too long")
    return value

def validate_nickname(value):
    validate_blank(value)
    validate_too_long(value, 20)
    return value

def validate_email(value):
    match = re.match(r'[a-zA-Z0-9_+-]+(.[a-zA-Z0-9_+-]+)*@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$', value)
    if not match:
        raise GraphQLError("Invalid Email Address")
    return value

def validate_password(value):
    validate_blank(value)
    if len(value) < 6:
        raise GraphQLError("Password is too short")
    api/schema.py

创建用户,获取令牌,删除用户,编辑个人资料的Mutation,以及获取自己的个人资料和所有个人资料的查询将被创建。
作为扩展功能,使用relay。

import graphene
import graphql_jwt

from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphene import relay
from graphql_jwt.decorators import login_required

from api.models import CustomUser, Profile
from api.utils.validator import validate_nickname, validate_email, validate_password

class UserNode(DjangoObjectType):
    class Meta:
        model = CustomUser
        filter_fields = {
            'email': ['exact'],
        }
        interfaces = (relay.Node,)

class ProfileNode(DjangoObjectType):
    class Meta:
        model = Profile
        filter_fields = {
            'nickname': ['exact', 'icontains'],
        }
        interfaces = (relay.Node,)


class CreateUserMutation(relay.ClientIDMutation):
    class Input:
        nickname = graphene.String(required=True)
        email = graphene.String(required=True)
        password = graphene.String(required=True)

    user = graphene.Field(UserNode)

    def mutate_and_get_payload(root, info, **input):
        nickname = validate_nickname(input.get('nickname'))
        email = validate_email(input.get('email'))
        password = validate_password(input.get('password'))
        user = CustomUser(
            email=email,
        )
        user.set_password(password)

        user.save()
        profile = Profile(
            nickname=nickname,
            user=user
        )
        profile.save()

        return CreateUserMutation(user=user)

class DeleteUserMutation(relay.ClientIDMutation):
    class Input:
        confirm = graphene.Boolean(required=True)

    user = graphene.Field(UserNode)

    @login_required
    def mutate_and_get_payload(root, info, **input):
        user = info.context.user
        user.delete()

        return DeleteUserMutation(user=None)

class UpdateProfileMutation(relay.ClientIDMutation):
    class Input:
        nickname = graphene.String(required=True)

    profile = graphene.Field(ProfileNode)

    @login_required
    def mutate_and_get_payload(root, info, **input):
        profile = info.context.user.profile
        profile.nickname = validate_nickname(input.get('nickname'))
        profile.save()

        return UpdateProfileMutation(profile=profile)

class Mutation(graphene.AbstractType):
    token_auth = graphql_jwt.ObtainJSONWebToken.Field()
    create_user = CreateUserMutation.Field()
    delete_user = DeleteUserMutation.Field()
    update_profile = UpdateProfileMutation.Field()

class Query(graphene.ObjectType):
    my_profile = graphene.Field(ProfileNode)
    all_profile = DjangoFilterConnectionField(ProfileNode)

    @login_required
    def resolve_my_profile(self, info, **kwargs):
        return Profile.objects.get(user=info.context.user.id)

    @login_required
    def resolve_all_profile(self, info, **kwargs):
        return Profile.objects.all()
    app/schema.py

将 api/schema.py 文件中的模式加载的方式进行描述。

import graphene
import api.graphql.schema

class Query(api.graphql.schema.Query, graphene.ObjectType):
    pass

class Mutation(api.graphql.schema.Mutation, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query, mutation=Mutation)
    urls.py

添加用于操作数据的端点。

  from django.contrib import admin
  from django.urls import path
+ from graphene_django.views import GraphQLView
+ from app.schema import schema
+ from django.views.decorators.csrf import csrf_exempt

  urlpatterns = [
      path('admin/', admin.site.urls),
+     path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
]

启动容器后,通过访问localhost:8000/graphql,即可显示GraphQL页面。

Graphqlのページ

如果在GraphQL页面的左侧进行以下描述,就可以进行一系列的数据操作。

mutation CREATE_USER{
  createUser(input: { nickname: "user", email: "user@example.com", password: "password" }) {
  	user {
      id
    }
  }
}

mutation TOKEN_AUTH{
  tokenAuth(email: "user@example.com", password: "password") {
    token
  }
}

mutation UPDATE_MYPROFILE{
  updateProfile(input: {nickname: "user update"}) {
    profile {
      nickname
    }
  }
}

mutation DELETE_USER {
  deleteUser(input: {confirm: true}) {
    user {
      id
    }
  }
}

query MY_PROFILE{
  myProfile {
    nickname
  }
}

query ALL_PROFILE{
  allProfile {
    edges {
      node {
        nickname
      }
    }
  }
}

如果用户没有进行令牌认证,则无法执行用户删除、个人资料编辑和个人资料获取操作。(假设在浏览器中使用Google Chrome)可以通过在Google Chrome中添加名为ModHeader的扩展来在请求标头中添加从graphql页面获取的令牌,从而使操作变得可行。

总结起来

我已经创建了与用户验证相关的用户模型和个人资料模型的模式,并写了操作数据的部分。
下次我想写测试Django项目模式的部分。

用Docker+Django+Next+TypeScript+ECS创建应用程序的故事(第4部分)——Django测试篇

广告
将在 10 秒后关闭
bannerAds