Implementing an application authentication mechanism similar to Tencent or Alibaba Cloud in django-ninja.
Django-Ninja API authentication mechanism? Isn't it just another authentication system based on AppClient, supporting query parameters, headers, and Bearer Token authentication, plus IP whitelisting and scopes permission control? The code is complex enough, convoluted enough?
目录37 个章节
Preface
This article introduces how to use the Django-Ninja API authentication mechanism based on the AppClient model.
This is also a derivative of the mid-platform project mentioned previously.
I will probably write another article related to the mid-platform project.
This series of articles is destined to be read by few people, as it is still somewhat niche.
However, I still need to write it. Even without readers, I want to document it so that I can use it in the future when needed.
PS: This article is based on the use of the DjangoStarter framework, assuming the reader is familiar with the project structure and infrastructure of DjangoStarter version 3.0 or above.
Due to space limitations, this article cannot include all the code. Interested readers can reply "API authentication" in the background of the WeChat public account to get the complete code.
Overview
This authentication mechanism provides the following features:
- ==Multiple authentication methods==: Supports query parameters, request headers, and Bearer Token
- ==IP Whitelist==: Supports individual IP addresses and CIDR network ranges
- ==Scope-based permission control==: Fine-grained permission management based on scopes
- ==Status check==: Automatically verifies the enabled status of AppClient
- ==Security==: Includes a complete authentication and authorization flow
It goes best with the official documentation~
https://django-ninja.dev/guides/authentication/
Usage
To give you an intuitive experience
I'll introduce the usage first
Then I'll explain the implementation.
1. Create AppClient
First, create an AppClient either in Django Admin or through code:
from apps.app.models import AppClient
app_client = AppClient.objects.create(
app_id="my-app",
app_name="My App",
secret_key="your-secret-key-here",
allowed_ips="192.168.1.0/24,10.0.0.1", # Optional: IP restriction
scopes="project:read,project:write,user:read", # Optional: scope
status=AppClient.Status.ACTIVE
)
2. Using Authentication in the API
from ninja import Router
from core.authentication import (
api_key_auth,
api_key_header_auth,
api_key_bearer_auth,
require_app_client_scopes,
AppClientScopes,
)
router = Router()
Authentication with Query Parameters
@router.get("/data", auth=api_key_auth) def get_data(request): app_client = request.auth return {"data": "success", "client": app_client.app_name}
Using Request Header Authentication
@router.get("/info", auth=api_key_header_auth) def get_info(request): return {"info": "success"}
Using Bearer Token Authentication
@router.get("/status", auth=api_key_bearer_auth) def get_status(request): return {"status": "success"}
### 3. Adding Permission Control
```python
# Requires Specific Permissions
@router.get("/projects", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def list_projects(request):
return {"projects": []}
# Requires Multiple Permissions
```python
@router.post("/projects", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_WRITE, AppClientScopes.USER_READ])
def create_project(request):
return {"message": "Project created successfully"}
Manual Permission Check
@router.get("/custom-check", auth=app_client_api_key) def custom_permission_check(request): from core.authentication import AppClientScopeChecker
if AppClientScopeChecker.has_any_scope(request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]):
return {"access": "granted"}
else:
return {"access": "denied"}
## Authentication Methods
### 1. Query Parameter Authentication (APIKeyQuery)
**Usage:**
```bash
GET /api/endpoint?api_key=your-secret-key
Code Example:
from core.authentication import api_key_auth
@router.get("/data", auth=api_key_auth)
def get_data(request):
return {"data": "success"}
2. API Key Header Authentication (APIKeyHeader)
Usage:
GET /api/endpoint
X-API-Key: your-secret-key
Code Example:
from core.authentication import api_key_header_auth
@router.get("/data", auth=api_key_header_auth)
def get_data(request):
return {"data": "success"}
3. Bearer Token Authentication (Bearer Token 认证)
Usage:
GET /api/endpoint
Authorization: Bearer your-secret-key
Code Example:
from core.authentication import api_key_bearer_auth
@router.get("/data", auth=api_key_bearer_auth)
def get_data(request):
return {"data": "success"}
Permission Scopes System
To clarify upfront, the scopes approach is simply the easiest to implement, but it doesn't mean it's the most appropriate.
After all, Django already has its own built-in permission system. I've considered integrating that system, which would allow for convenient editing of permissions for each application in the admin panel.
However, since that implementation would be slightly more complicated, and for the sake of getting things launched quickly, I initially went with creating my own scopes system.
I definitely plan to try ==integrating the Django permission system== in the future!
Permission Format
Permissions use the resource:action format. For example:
project:read- Project read permissionsproject:write- Project write permissionsuser:*- All user-related permissions*- Super administrator permissions
Predefined Permission Constants
from core.authentication import AppClientScopes
Project Permissions
AppClientScopes.PROJECT_READ # "project:read" AppClientScopes.PROJECT_WRITE # "project:write" AppClientScopes.PROJECT_DELETE # "project:delete" AppClientScopes.PROJECT_ALL # "project:*"
User Permissions
AppClientScopes.USER_READ # "user:read" AppClientScopes.USER_WRITE # "user:write" AppClientScopes.USER_DELETE # "user:delete" AppClientScopes.USER_ALL # "user:*"
Super Privileges
AppClientScopes.SUPER_ADMIN # "*"
### Permission Checking Methods
#### 1. Decorator Approach (Recommended)
```python
from core.authentication import require_app_client_scopes, AppClientScopes
@router.get("/projects", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def list_projects(request):
return {"projects": []}
2. Manual Check Approach
from core.authentication import AppClientScopeChecker
@router.get("/data", auth=app_client_api_key)
def get_data(request):
# Check if all specified permissions are present
if AppClientScopeChecker.check_scopes(request, [AppClientScopes.PROJECT_READ]):
return {"data": "success"}
# Check if any one of the permissions is present
if AppClientScopeChecker.has_any_scope(request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]):
return {"data": "partial access"}
# Get all client permissions
scopes = AppClientScopeChecker.get_client_scopes(request)
return {"error": "Insufficient permissions", "your_scopes": scopes}
IP Whitelist
Configure IP Restrictions
Configure the allowed IP addresses in the allowed_ips field of AppClient:
# Single IP
app_client.allowed_ips = "192.168.1.100"
# Multiple IPs
app_client.allowed_ips = "192.168.1.100,10.0.0.1,203.0.113.5"
# CIDR Subnet
app_client.allowed_ips = "192.168.1.0/24,10.0.0.0/8"
# Mixed Configuration
app_client.allowed_ips = "192.168.1.100,10.0.0.0/8,203.0.113.0/24"
# No IP restrictions (leave empty or None)
app_client.allowed_ips = None
IP Acquisition Logic
The system will acquire the client IP address according to the following priority:
- The first IP from the
HTTP_X_FORWARDED_FORheader (applicable in proxy/load balancing environments) REMOTE_ADDR(the IP of the direct connection)
Error Handling
Authentication Failed
When authentication fails, an HTTP 401 error will be returned:
{
"detail": "Unauthorized"
}
Insufficient Permissions
When a permission check fails, an HTTP 403 error is returned:
{
"detail": "缺少权限: project:write"
}
Custom Error Handling
from core.authentication import AppClientPermissionError
@router.get("/custom", auth=app_client_api_key)
def custom_endpoint(request):
if not some_condition:
raise AppClientPermissionError("自定义权限错误信息")
return {"data": "success"}
Test API
The project provides complete test APIs that can be used to verify authentication and permission features:
# Test API Key Authentication
GET /api/app/test/api-key?api_key=your-secret-key
# Test Header Authentication
GET /api/app/test/api-key-header
X-API-Key: your-secret-key
# Testing Bearer Token Authentication
GET /api/app/test/bearer
Authorization: Bearer your-secret-key
# Test Permission Scopes
GET /api/app/test/scopes/project-read?api_key=your-secret-key
GET /api/app/test/scopes/project-write?api_key=your-secret-key
GET /api/app/test/scopes/multiple?api_key=your-secret-key
## Best Practices
Here are some recommendations for best practices
Regardless of the technology or framework used, these can be referenced during actual development
### 1. Key Management
- Use strong keys (recommended 32 characters or more)
- Rotate keys regularly
- Do not hard-code keys in the code
- Use environment variables or a secure configuration management system
### 2. Permission Design
- Adhere to the **Least Privilege Principle**
- Use **fine-grained permission scopes**
- Regularly review and clean up unnecessary permissions
- Create different **AppClients** for different application scenarios
### 3. IP Restrictions
- Enable IP whitelist in the production environment
- Use CIDR ranges instead of individual IPs (for easier scaling)
- Consider CDN and proxy IP forwarding
### 4. Monitoring and Logging
```python
import logging
logger = logging.getLogger(__name__)
@router.get("/sensitive-data", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def get_sensitive_data(request):
app_client = request.auth
logger.info(f"AppClient {app_client.app_id} accessed sensitive data")
return {"data": "sensitive information"}
Integrate into Existing API
Adding Routes
Add the app route in config/apis.py:
from apps.app.apis import router as app_router
api.add_router('app', app_router)
Hybrid Authentication
This is a new functionality provided by django-ninja
It can support multiple authentication methods simultaneously within the same API:
from ninja.security import django_auth
from core.authentication import api_key_auth
Simultaneously Supporting Django User Authentication and AppClient Authentication
@router.get("/user-data", auth=[django_auth, api_key_auth]) def get_data(request): return {"user": request.user.username}
### Update Main API Configuration
In addition to this, you can configure global authentication and other features in the main API configuration.
```python
from django.contrib.admin.views.decorators import staff_member_required
api = NinjaAPI(
title=f'{settings.DJANGO_STARTER["project_info"]["name"]} APIs',
description=settings.DJANGO_STARTER["project_info"]["description"],
renderer=JSONRespRenderer(),
csrf=True,
auth=[django_auth, api_key_auth, api_key_header_auth, api_key_bearer_auth],
urls_namespace='api',
docs=Swagger(settings={"persistAuthorization": True}),
docs_decorator=staff_member_required,
)
Here is a brief introduction:
My configuration:
- Uses
auth=[django_auth, api_key_auth, api_key_header_auth, api_key_bearer_auth]to support multiple authentication methods (there is also a JWT one, but I haven't considered it here). - Uses
docs_decorator=staff_member_required,to configure that accessing the Swagger documentation requires a logged-instaff_member(职员成员).
Implementation
OK, finally, let me introduce the specific implementation (I doubt anyone has scrolled this far…)
Due to space constraints, this article cannot include all the code. Interested readers can reply with "API authentication (API鉴权)" in the background of our official account to get the complete code.
Model
First is the AppClient model, with its code located in src/apps/app/models.py.
In addition to field definitions, several methods have been added for functionalities such as ==getting the list of permission scopes== and ==getting the list of allowed IP addresses==.
According to DDD principles, this model can be considered a ==rich domain model== 😃
import uuid
from django.db import models
from django.core.validators import RegexValidator
from django_starter.db.models import ModelExt
class AppClient(ModelExt):
"""Application Client Model for managing third-party application access permissions"""
class Status(models.TextChoices):
ACTIVE = 'active', '启用'
INACTIVE = 'inactive', '禁用'
id = models.BigAutoField(
primary_key=True,
verbose_name='主键ID',
help_text='系统自动生成的唯一标识符',
db_comment='物理主键,自增ID'
)
guid = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
verbose_name='全局唯一标识',
help_text='系统自动生成的UUID,用作逻辑主键',
db_comment='逻辑主键,GUID'
)
app_id = models.CharField(
max_length=100,
unique=True,
verbose_name='应用标识',
help_text='用于标识调用方系统的唯一ID,建议使用有意义的命名',
db_comment='应用标识,用于标识调用方系统',
validators=[
RegexValidator(
regex=r'^[a-zA-Z0-9_-]+$',
message='应用标识只能包含字母、数字、下划线和连字符'
)
]
)
app_name = models.CharField(
max_length=200,
verbose_name='应用名称',
help_text='调用方应用的显示名称或备注信息',
db_comment='应用名称,调用方的名称或备注信息'
)
secret_key = models.CharField(
max_length=100,
verbose_name='密钥',
help_text='用于API签名验证的密钥,请妥善保管',
db_comment='密钥,用于签名验证',
)
allowed_ips = models.TextField(
blank=True,
null=True,
verbose_name='允许访问的IP',
help_text='允许访问的IP地址列表,多个IP用逗号分隔,留空表示不限制',
db_comment='允许访问的 IP 列表,逗号分隔'
)
scopes = models.TextField(
blank=True,
null=True,
verbose_name='权限范围',
help_text='应用的权限范围,格式如:project:read,project:write',
db_comment='权限范围,如 project:read,project:write'
)
status = models.CharField(
max_length=10,
choices=Status.choices,
default=Status.ACTIVE,
verbose_name='状态',
help_text='应用的启用状态',
db_comment='启用/禁用状态'
)
class Meta:
db_table = 'app_client'
verbose_name = '应用客户端'
verbose_name_plural = '应用客户端'
ordering = ['-create_time']
indexes = [
models.Index(fields=['app_id']),
models.Index(fields=['status']),
models.Index(fields=['create_time']),
]
def __str__(self):
return f'{self.app_name} ({self.app_id})'
def is_active(self):
"""Check if the application is in an active state"""
# todo 为什么这个 is_deleted 会变成 str 啊??
# return self.status == self.Status.ACTIVE and not self.is_deleted
return self.status == self.Status.ACTIVE
def get_scopes_list(self):
"""Get the list of permission scopes"""
if not self.scopes:
return []
return [scope.strip() for scope in self.scopes.split(',') if scope.strip()]
def get_allowed_ips_list(self):
"""Get the list of allowed IP addresses"""
if not self.allowed_ips:
return []
return [ip.strip() for ip in self.allowed_ips.split(',') if ip.strip()]
def save(self, *args, **kwargs):
"""Override save method to add custom logic"""
# Ensure app_name is not empty
if not self.app_name:
self.app_name = self.app_id
super().save(*args, **kwargs)
Test Interfaces
Add some test interfaces for easier debugging later
The code is located in src/apps/app/apis/client/apis.py
from typing import List
from ninja import Router
from django.http import HttpRequest
from core.authentication import (
api_key_auth,
api_key_header_auth,
api_key_bearer_auth,
require_app_client_scopes,
AppClientScopes,
AppClientScopeChecker,
)
from apps.app.models import AppClient
from .schemas import AppClientSchema, AppClientCreateSchema
router = Router(tags=['AppClient API'])
@router.get("/test/api-key", auth=api_key_auth)
def test_api_key_auth(request: HttpRequest):
"""测试API Key认证(查询参数方式)
使用方式: GET /api/app/test/api-key?api_key=your_secret_key
"""
app_client = request.auth
return {
"message": "API Key认证成功",
"app_id": app_client.app_id,
"app_name": app_client.app_name,
"scopes": app_client.get_scopes_list(),
}
@router.get("/test/api-key-header", auth=api_key_header_auth)
def test_api_key_header_auth(request: HttpRequest):
"""测试API Key认证(请求头方式)
使用方式: GET /api/app/test/api-key-header
Headers: X-API-Key: your_secret_key
"""
app_client = request.auth
return {
"message": "API Key Header认证成功",
"app_id": app_client.app_id,
"app_name": app_client.app_name,
"scopes": app_client.get_scopes_list(),
}
@router.get("/test/bearer", auth=api_key_bearer_auth)
def test_bearer_auth(request: HttpRequest):
"""测试Bearer Token认证
使用方式: GET /api/app/test/bearer
Headers: Authorization: Bearer your_secret_key
"""
app_client = request.auth
return {
"message": "Bearer Token认证成功",
"app_id": app_client.app_id,
"app_name": app_client.app_name,
"scopes": app_client.get_scopes_list(),
}
@router.get("/test/scopes/project-read", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def test_project_read_scope(request: HttpRequest):
"""测试项目读取权限
需要权限: project:read
"""
return {
"message": "项目读取权限验证成功",
"required_scope": AppClientScopes.PROJECT_READ,
"client_scopes": AppClientScopeChecker.get_client_scopes(request),
}
@router.get("/test/scopes/project-write", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_WRITE])
def test_project_write_scope(request: HttpRequest):
"""测试项目写入权限
需要权限: project:write
"""
return {
"message": "项目写入权限验证成功",
"required_scope": AppClientScopes.PROJECT_WRITE,
"client_scopes": AppClientScopeChecker.get_client_scopes(request),
}
@router.get("/test/scopes/multiple", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_READ, AppClientScopes.USER_READ])
def test_multiple_scopes(request: HttpRequest):
"""测试多个权限要求
需要权限: project:read AND user:read
"""
return {
"message": "多权限验证成功",
"required_scopes": [AppClientScopes.PROJECT_READ, AppClientScopes.USER_READ],
"client_scopes": AppClientScopeChecker.get_client_scopes(request),
}
@router.get("/test/scopes/any", auth=api_key_auth)
def test_any_scope(request: HttpRequest):
"""测试任意权限检查
检查是否具有项目或用户相关的任意权限
"""
has_project_access = AppClientScopeChecker.has_any_scope(
request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]
)
has_user_access = AppClientScopeChecker.has_any_scope(
request, [AppClientScopes.USER_READ, AppClientScopes.USER_WRITE]
)
return {
"message": "权限检查完成",
"has_project_access": has_project_access,
"has_user_access": has_user_access,
"client_scopes": AppClientScopeChecker.get_client_scopes(request),
}
@router.get("/info", auth=api_key_auth)
def get_client_info(request: HttpRequest):
"""获取当前认证的AppClient信息"""
app_client = request.auth
return {
"app_id": app_client.app_id,
"app_name": app_client.app_name,
"status": app_client.status,
"scopes": app_client.get_scopes_list(),
"allowed_ips": app_client.get_allowed_ips_list(),
"create_time": app_client.create_time,
}
@router.get("/clients", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.SUPER_ADMIN])
def list_app_clients(request: HttpRequest) -> List[dict]:
"""列出所有AppClient(需要超级管理员权限)
需要权限: *
"""
clients = AppClient.objects.filter(is_deleted=False)
return [
{
"app_id": client.app_id,
"app_name": client.app_name,
"status": client.status,
"scopes": client.get_scopes_list(),
"create_time": client.create_time,
}
for client in clients
]
Management Commands
For convenience, I've added two more commands
As the name suggests, ==Create a new AppClient== and ==List all==
src/apps/app/management/commands/create_app_client.pysrc/apps/app/management/commands/list_app_clients.py
Due to space limitations, I can't include all the code here. If you're interested, you can reply with "API Authentication (API鉴权)" in the backend of the official account to get the complete code.
Core Logic
Regarding the core logic of authentication, I have created a separate core package to store it.
The directory structure is as follows:
core
├─ exceptions
│ ├─ __init__.py
│ └─ permissions.py
├─ authentication
│ ├─ __init__.py
│ ├─ README.md
│ ├─ permissions.py
│ ├─ app.py
│ └─ api_key.py
└─ __init__.py
The most critical code consists of these authentication instances in src/core/authentication/app.py.
The code contains detailed comments, making it easy to understand at a glance.
If you find this still a bit complex, I will later introduce an extremely simple implementation.
class AppClientAuthMixin:
"""AppClient authentication mixin class, providing common authentication logic"""
def _get_client_ip(self, request: HttpRequest) -> str:
"""Get the client's real IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def _validate_ip_access(self, app_client: AppClient, request: HttpRequest) -> bool:
"""Validate IP access permissions"""
allowed_ips = app_client.get_allowed_ips_list()
if not allowed_ips: # If no IP restrictions are set, allow all IPs
return True
client_ip = self._get_client_ip(request)
for allowed_ip in allowed_ips:
try:
# Support single IPs and CIDR ranges
if '/' in allowed_ip:
network = ipaddress.ip_network(allowed_ip, strict=False)
if ipaddress.ip_address(client_ip) in network:
return True
else:
if client_ip == allowed_ip:
return True
except (ipaddress.AddressValueError, ValueError):
# Ignore invalid IP formats
continue
return False
def _authenticate_app_client(self, request: HttpRequest, key: str) -> Optional[AppClient]:
"""General logic for authenticating an AppClient"""
try:
app_client = AppClient.objects.get(secret_key=key)
# Check if the application is active
if not app_client.is_active():
return None
# Validate IP access permissions
if not self._validate_ip_access(app_client, request):
return None
# Add permission scopes to the request for subsequent permission checks
request.app_client_scopes = app_client.get_scopes_list()
return app_client
except AppClient.DoesNotExist:
return None
class AppClientApiKey(AppClientAuthMixin, APIKeyQuery):
"""AppClient API Key authentication based on query parameters"""
param_name = "api_key"
def authenticate(self, request: HttpRequest, key: str) -> Optional[AppClient]:
return self._authenticate_app_client(request, key)
class AppClientApiKeyHeader(AppClientAuthMixin, APIKeyHeader):
"""AppClient API Key authentication based on request headers"""
param_name = "X-API-Key"
def authenticate(self, request: HttpRequest, key: str) -> Optional[AppClient]:
return self._authenticate_app_client(request, key)
class AppClientBearer(AppClientAuthMixin, HttpBearer):
"""AppClient authentication based on Bearer Token"""
def authenticate(self, request: HttpRequest, token: str) -> Optional[AppClient]:
return self._authenticate_app_client(request, token)
Create authentication instances, available for direct use in the API
api_key_auth = AppClientApiKey() api_key_header_auth = AppClientApiKeyHeader() api_key_bearer_auth = AppClientBearer()
## Minimal Implementation
This complete authentication logic may seem overly complex to you
No worries, I've also created a minimal version. It removes permissions, IP restrictions and other extraneous elements, only validating whether the api-key is valid.
The implementation code is in `src/core/authentication/api_key.py`
The custom `ApiKey` class inherits from `ninja.security.APIKeyQuery` class, indicating that the `api_key` parameter needs to be included in the **GET query params**.
```python
from ninja.security import APIKeyQuery
from apps.app.models import AppClient
class ApiKey(APIKeyQuery):
param_name = "api_key"
def authenticate(self, request, key):
try:
c = AppClient.objects.get(secret_key=key)
print(f'api key authenticated: {c.secret_key}, app id: {c.app_id}')
return c
except AppClient.DoesNotExist:
pass
Usage is also straightforward:
from apps.app.models import AppClient
from core.authentication.api_key import ApiKey
@router.get("/data", auth=ApiKey())
def get_data(request):
assert isinstance(request.auth, AppClient)
return {"data": "success"}
When making client requests, include the secret_key of the AppClient in the ==GET query params==.
If authentication succeeds, ninja will automatically add the AppClient instance to request.auth, allowing you to directly access this instance in the endpoint code.
Summary
This article actually ended up over 20,000 words long 🕶
And this is after removing a lot of code...
One more time for those interested: you can reply with API Authentication in the back end of our public account to get the complete code.
Such a long article, probably no one will have the patience to read all the way to the end, right~
Peace out, peace out~
_EOF_
Implementing an application authentication mechanism similar to Tencent or Alibaba Cloud in django-ninja.
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
Django中间件之实现Admin后台IP白名单
前言 Django自带的Admin管理后台很方便,但是实际生产环境真的会有挺多安全问题的,在admin的安全防护这方面,我之前就研究实现了给admin加上登录验证码和限流功能,可以参考这篇文章:给Django Admin添加验证码和多次登录尝试限制 不过就在内部使用的话就好很多,因此我们可以通过Django的中间件来实现IP过滤,所以本文中涉及到两个知识点,一个是Django的中间件,另外一个是P
2022-08-26 · Django · 约 1 分钟
Django的单元测试和集成测试
测试,在以前确实是经常被忽略的话题,特别是对于 Python Web 这种快速开发框架
2024-03-06 · Django · 约 1 分钟
Django统一包装接口返回数据格式
前言 最近实在太忙了,开始了一个新的项目,为了快速形成产品,我选择了Django来实现后端,然后又拿起了之前我封装了项目脚手架「DjangoStarter」。 由于前段时间我写了不少.NetCore的后端代码,对CRUD的接口开发又有了一些新的理解,所以肯定也要把DjangoStarter改造一番,改得更加顺手~ 题外话:话说我在前端终于真正用上react,不得不说 ts + react 真好用,
2022-08-26 · Django · 约 1 分钟
告别单调,Django后台主页改造
前言 之前我做了个Django的项目,为了让管理后台更加美观,我对Django(应该说是SimpleUI的)默认的Admin后台主页进行改造,具体可以看这篇文章:项目完成 - 基于Django3.x版本 - 开发部署小结 之前的两篇关于Django3.x开发部署小结的文章介绍的都是数据库、接口、性能方面的,看到有朋友留言对后台主页的改造表示有兴趣,所以就写一篇来介绍一下~ 分析 改造基于我定制的
2022-06-28 · Django · 约 1 分钟
讨论
共 0 条加载评论中…