# ninjaextra_bookstore **Repository Path**: windstarry/ninjaextra_bookstore ## Basic Information - **Project Name**: ninjaextra_bookstore - **Description**: 使用django-ninja-extra构建的bookstore后端接口 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-27 - **Last Updated**: 2024-02-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 使用django-ninja-extra构建bookstore项目 ## 项目初始化 ### 1. 创建项目 ```shell django-admin startproject bookstore ``` ### 2. 进入项目目录 ```shell cd bookstore ``` ### 3. 创建虚拟环境 ```shell python -m venv .venv # 激活虚拟环境 venv\scripts\activate.ps1 ``` ### 4. 安装依赖 ```shell python -m pip install -r requirements.txt ``` ### 5. 启动项目 ```shell python manage.py runserver ``` ### 6. 配置django中文显示 **settings.py中修改语言和时区** ```python LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' ``` ### 7. 配置ninja-extra **settings.py**中添加ninja_extra到INSTALLED_APPS中 ```python INSTALLED_APPS = [ ..., 'ninja_extra', ] ``` ### 8. 编写路由 **在urls.py同目录下创建新文件:api.py** ```python from ninja_extra import NinjaExtraAPI, api_controller, route api = NinjaExtraAPI(title="后端管理接口", description="使用DjangoNinjaextra实现的后端管理接口", version="1.0.0") # 传统方法 @api.get("/add", tags=['Math'],summary='加法计算') def add(request, a: int, b: int): return {"result": a + b} # 基于类的方法 @api_controller('/', tags=['Math'], permissions=[]) class MathAPI: @route.get('/subtract',summary='减法计算') def subtract(self, a: int, b: int): """Subtracts a from b""" return {"result": a - b} @route.get('/divide',summary='除法计算') def divide(self, a: int, b: int): """Divides a by b""" return {"result": a / b} @route.get('/multiple',summary='乘法计算') def multiple(self, a: int, b: int): """Multiples a with b""" return {"result": a * b} api.register_controllers( MathAPI ) ``` **在urls.py中添加路由** ```python from django.urls import path from .api import api urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), # <---------- ! ] ``` ### 9. 查看swagger文档 转到 http://127.0.0.1:8000/api/docs, 可以看到Swagger UI生成的交互式文档 ### 10. 使用env加载环境变量 Django项目中使用.env文件来管理不同的环境变量 #### 10.1 安装python-dotenv ```shell pip install python-dotenv ``` #### 10.2 创建.env文件 在项目根目录下创建.env文件,并在文件中添加需要定义的环境变量。 在开发环境下定义.env文件,将敏感信息写入.env文件 这里数据库为bookstore ```text SECRET_KEY=django-insecure-ql+&96qf5ra-m2a9-8fj0q)%u#o4z*0_r#1f^ayea=969sxuy8 DEBUG=True #数据库连接相关设置 DATABASE_NAME=bookstore DATABASE_USER=root DATABASE_PASSWORD=123456 DATABASE_HOST=localhost DATABASE_PORT=3306 ``` ### 11. 修改数据库为mysql **安装mysqlclient** ```shell pip install mysqlclient ``` **在bookstore/settings.py中加载环境变量** ```python import os from dotenv import load_dotenv # 使用默认的.env文件名。也可以指定不同的文件名。 load_dotenv() # 开启或关闭调试模式 DEBUG = os.getenv('DEBUG', False) SECRET_KEY = os.getenv('SECRET_KEY', 'default') # 修改数据库为mysql DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": os.getenv("DATABASE_NAME", "default"), "USER": os.getenv("DATABASE_USER", "default"), "PASSWORD": os.getenv("DATABASE_PASSWORD", ""), "HOST": os.getenv("DATABASE_HOST", ""), "PORT": os.getenv("DATABASE_PORT", ""), "OPTIONS": { "charset": "utf8mb4", }, } } ``` ### 12. 指定静态文件目录 在**bookstore/settings.py**中添加静态文件目录 ```python STATIC_URL = 'static/' # 定义静态资源位置 STATIC_ROOT = os.path.join(BASE_DIR, "static") # 在项目根目录下新建media目录 追加内容 # 图片资源访问路径,注意新建media这个目录 MEDIA_URL = 'media/' # 图片资源存放路径 MEDIA_ROOT =os.path.join(BASE_DIR, "media") ``` ### 13. 修改url的静态文件路由 修改**bookstore/urls.py**中的路由 ```python from django.conf.urls.static import static from bookstore import settings urlpatterns = [ path('admin/', admin.site.urls), path("api/", api.urls), ]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` ## 创建应用 ### 1. 创建system ```shell python manage.py startapp system ``` 在Django中注册app 在**bookstore/settings.py**中添加app到INSTALLED_APPS中 ```python INSTALLED_APPS = [ ...... 'system', ] ``` ### 2. 创建core模型 新建core文件夹,创建**models.py** ```python from django.db import models from bookstore import settings class CoreModel(models.Model): """ 核心标准抽象模型模型,可直接继承使用 """ id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id") description = models.CharField( max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述" ) creator = models.ForeignKey(to = settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False) modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人") update_datetime = models.DateTimeField( auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间" ) create_datetime = models.DateTimeField( auto_now_add=True, null=True, blank=True, help_text="创建时间", verbose_name="创建时间" ) sort = models.IntegerField( default=1, null=True, blank=True, verbose_name="显示排序", help_text="显示排序" ) class Meta: abstract = True verbose_name = "核心模型" verbose_name_plural = verbose_name ``` 修改**bookstore/settings.py**中的users配置 ```python AUTH_USER_MODEL = "system.Users" USERNAME_FIELD = "username" ``` ### 3. 创建Users模型 修改**system/models.py** ```python class Users(AbstractUser, CoreModel): username = models.CharField( max_length=150, unique=True, db_index=True, verbose_name="用户账号", help_text="用户账号", ) email = models.EmailField( max_length=255, verbose_name="用户邮箱", null=True, blank=True, help_text="用户邮箱" ) avatar = models.ImageField(upload_to="avatars/", verbose_name="用户头像", default="avatars/default.jpg",null=True, blank=True, help_text="用户头像") nickname = models.CharField(max_length=40, null=True, blank=True,verbose_name="用户昵称", help_text="用户昵称") status = models.BooleanField(default=True, verbose_name="状态", help_text="状态") GENDER_CHOICES = ( (0, "未知"), (1, "男"), (2, "女"), ) gender = models.IntegerField( choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True, help_text="性别", ) class Meta: db_table = "system_users" verbose_name = "用户管理" verbose_name_plural = verbose_name ordering = ("id",) def __str__(self) -> str: return self.username ``` ### 4. 修改admin的用户模型 修改**system/admin.py** ```python from .models import Users @admin.register(Users) class UserAdmin(admin.ModelAdmin): list_display = ("username", "nickname", "email", "avatar") search_fields = ("username", "email") list_filter = ("username",) ordering = ("id",) ``` ### 5.生成模型类,创建超级用户 ```shell # 生成迁移文件 python manage.py makemigrations # 生成DB表 python manage.py migrate # 创建后台管理系统账号 admin, 执行命令进入交互窗口 python manage.py createsuperuser --username admin ``` ### 6. 创建用户路由 - 新建**system/controllers.py.py** ```python from ninja_extra import api_controller, route @api_controller("/users", tags=["用户管理"]) class UserController: @route.get( "/info", summary="用户信息" ) def get_user_info(self,): return { "code": 200, "msg": "ok", "data": { "username": "admin", }, } ``` - 在**bookstore/api.py**中引入路由 ```python from ninja_extra import NinjaExtraAPI from system.controllers import UserController api = NinjaExtraAPI(title="后端管理接口", description="使用DjangoNinjaextra实现的后端管理接口", version="1.0.0") api.register_controllers( UserController, ) ``` 确定测试显示结果正常 ### 7. 创建用户、Token相关Schema 新建**system/schemas.py**,添加用户,Token相关Schema ```python from pydantic import validator from datetime import datetime from typing import List, Optional, Type from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from ninja import Field from ninja_schema import ModelSchema, Schema, model_validator from ninja_extra import status from ninja_extra.exceptions import APIException UserModel = get_user_model() class GroupSchema(ModelSchema): class Config: model = Group include = ("name",) # 用户登录请求字段 class LoginSchema(Schema): username: str = Field(..., description="用户名") password: str = Field(..., description="密码") # 用户创建请求字段 class CreateUserSchema(ModelSchema): class Config: model = UserModel include = ( "username", "email", "password", ) @model_validator("username") def unique_name(cls, value_data): if UserModel.objects.filter(username__icontains=value_data).exists(): raise APIException("用户名已存在", code=status.HTTP_400_BAD_REQUEST) return value_data def create(self) -> Type[UserModel]: return UserModel.objects.create_user(**self.dict()) class UserRetrieveSchema(ModelSchema): groups: List[GroupSchema] class Config: model = UserModel include = ("id", "username", "email", "status") class UserTokenOutSchema(Schema): token: str user: UserRetrieveSchema token_exp_date: Optional[datetime] class EnableDisableUserSchema(Schema): user_id: int _user = None @validator( "user_id", ) def validate_user_id(cls, value): user = UserModel.objects.filter(id=value).first() if user: cls._user = user return value raise ValueError("用户Id无效") def update(self): self._user.status = not self._user.status self._user.save() return self._user def delete(self): _id = self._user.id self._user.delete() return _id class EnableDisableUserOutSchema(Schema): message: str ``` ### 8. 使用ninja-jwt实现token认证 #### 8.1 安装ninja-jwt ```shell pip install django-ninja-jwt ``` #### 8.2 修改settings.py设置 在**bookstore/settings.py**中修改ninja-jwt的设置和时区 ```python NINJA_JWT = { "AUTH_TOKEN_CLASSES": ("ninja_jwt.tokens.SlidingToken",), "SLIDING_TOKEN_LIFETIME": datetime.timedelta(hours=5), "SLIDING_TOKEN_REFRESH_LIFETIME": datetime.timedelta(days=1), } # 修改为使用当前时区 USE_TZ = False ``` #### 8.3 创建jwt认证路由 在**system/controllers.py**中添加jwt认证路由 ```python from datetime import datetime from ninja_jwt.controller import TokenObtainSlidingController from ninja_jwt.tokens import SlidingToken # --这里导入的是ninja_jwt的schema-- from ninja_jwt import schema from .schema import UserTokenOutSchema @api_controller("/auth", tags=["认证"]) class UserTokenController(TokenObtainSlidingController): # 设置自动导入jwt路由 auto_import = True @route.post("/login", response=UserTokenOutSchema, summary="登录", url_name="login") def obtain_token(self, user_token: schema.TokenObtainSlidingSerializer): user_obj = user_token._user token = SlidingToken.for_user(user_obj) return UserTokenOutSchema( user=user_obj, token=str(token), # 设置当前时区的时间戳 token_exp_date=datetime.fromtimestamp(token["exp"]), ) @route.post( "/api-token-refresh", response=schema.TokenRefreshSlidingSerializer, summary="刷新token", url_name="refresh", ) def refresh_token(self, refresh_token: schema.TokenRefreshSlidingSchema): refresh = schema.TokenRefreshSlidingSerializer(**refresh_token.dict()) return refresh ``` 这里也可以使用LoginSchema的方式生成token,更加灵活 ```python from .schema import LoginSchema from django.contrib.auth import get_user_model, authenticate @api_controller("/auth", tags=["认证"]) class UserTokenController(TokenObtainSlidingController): @route.post("/login", response=UserTokenOutSchema, summary="登录", url_name="login") def obtain_token(self, auth: LoginSchema): user_obj = authenticate(username=auth.username, password=auth.password) # 生成令牌 token = SlidingToken.for_user(user_obj) return UserTokenOutSchema( user=user_obj, token=str(token), # 设置当前时区的时间戳,设置为UTC时间戳使用datetime.utcfromtimestamp() token_exp_date=datetime.fromtimestamp(token["exp"]), ) ``` #### 8.4 api中引入jwt认证 在**bookstore/api.py**中添加jwt认证路由 ```python from ninja_extra import NinjaExtraAPI from system.controllers import UserController,UserTokenController api = NinjaExtraAPI(title="后端管理接口", description="使用DjangoNinjaextra实现的后端管理接口", version="1.0.0") api.register_controllers( UserController, UserTokenController, ) ``` ### 9. 创建用户管理路由 在**system/controllers.py**中添加用户管理路由 ```python from ninja_extra import api_controller, route, status from ninja_extra.permissions import IsAdminUser, IsAuthenticated from ninja_jwt import schema from ninja_jwt.authentication import JWTAuth from ninja_jwt.controller import TokenObtainSlidingController from ninja_jwt.tokens import SlidingToken from .schema import ( LoginSchema, CreateUserSchema, EnableDisableUserOutSchema, EnableDisableUserSchema, UserTokenOutSchema, ) User = get_user_model() @api_controller("/users", tags=["用户管理"],auth=JWTAuth()) class UserController: @route.get( "/info", summary="用户信息", response = {201: UserRetrieveSchema}, permissions=[IsAuthenticated], ) def get_user_info(self,): """ 获取用户信息 :return: UserRetrieveSchema对象,包含用户信息 """ user_obj = self.context.request.user return user_obj @route.post( "/register", response={201: UserTokenOutSchema}, summary="用户注册", url_name="user-create", auth=None, ) def register(self, user_schema: CreateUserSchema): user = user_schema.create() token = SlidingToken.for_user(user) return UserTokenOutSchema( user=user, token=str(token), token_exp_date=datetime.utcfromtimestamp(token["exp"]), ) @route.put( "/{int:pk}/enable-disable", response=EnableDisableUserOutSchema, summary="用户状态", url_name="user-enable-disable", permissions=[IsAuthenticated, IsAdminUser], ) def enable_disable_user(self, pk: int): user_schema = EnableDisableUserSchema(user_id=pk) user_schema.update() return EnableDisableUserOutSchema(message="更新成功") @route.delete( "/{int:pk}/delete", response=EnableDisableUserOutSchema, summary="用户删除", url_name="user-delete", permissions=[IsAuthenticated, IsAdminUser], ) def delete_user(self, pk: int): user_schema = EnableDisableUserSchema(user_id=pk) user_schema.delete() return self.create_response("", status_code=status.HTTP_204_NO_CONTENT) ``` > **注意:** > 通过auth=JWTAuth()进行token认证 > 通过permissions=[IsAuthenticated, IsAdminUser]权限控制 ## 创建业务应用 ### 1. 创建app ```shell django-admin startapp storeapp ``` 在Django中注册storeapp 在**bookstore/settings.py**中添加app到INSTALLED_APPS中 ```python INSTALLED_APPS = [ ...... 'storeapp', ] ``` ### 2. 创建storeapp的路由 在**storeapp/controllers.py**中添加路由 ```python from django.contrib.auth import get_user_model from ninja_extra import api_controller, route, status User = get_user_model() @api_controller("/books", tags=["书籍管理"]) class BooksController: @route.get("/info",summary="书籍信息",url_name="book-info") def get_book_info(self,): return {"message": "book info"} @api_controller("/stores", tags=["仓储管理"]) class StoresController: @route.get("/info",summary="仓储信息",url_name="store-info") def get_store_info(self,): return {"message": "store info"} ``` 在**storeapp/api.py**中添加路由 ```python from ninja_extra import NinjaExtraAPI from system.controllers import UserController,UserTokenController from storeapp.controllers import BooksController,StoresController api = NinjaExtraAPI(title="后端管理接口", description="使用DjangoNinjaextra实现的后端管理接口", version="1.0.0") api.register_controllers( UserController, UserTokenController, BooksController, StoresController, ) ``` 测试路由接口是否正常 ### 3. 创建storeapp的模型 修改**storeapp/models.py**中的模型 ```python ``` ### 4. 生成模型类 ```shell # 生成迁移文件 python manage.py makemigrations # 生成DB表 python manage.py migrate ```