代码上传

This commit is contained in:
2025-11-17 18:22:56 +08:00
commit 3a7b41ebce
66 changed files with 10186 additions and 0 deletions

View File

@@ -0,0 +1,438 @@
# Hertz Studio Django Utils 模块
## 概述
Hertz Studio Django Utils 是一个功能丰富的工具类模块,为 Hertz Server Django 项目提供核心的工具函数、响应格式、验证器和加密服务。该模块采用模块化设计,便于维护和扩展。
## 功能特性
- 🔐 **加密工具**: 提供多种加密算法和密码哈希功能
- 📧 **邮件服务**: 统一的邮件发送接口和验证码邮件模板
- 📋 **响应格式**: 标准化的API响应格式和错误处理
-**数据验证**: 邮箱、密码、手机号等格式验证
- 🧩 **模块化设计**: 各功能模块独立,便于按需使用
## 模块结构
```
hertz_studio_django_utils/
├── __init__.py # 模块初始化
├── crypto/ # 加密工具
│ ├── __init__.py
│ ├── encryption_utils.py # 通用加密工具
│ └── password_hashers.py # 密码哈希器
├── email/ # 邮件服务
│ ├── __init__.py
│ └── email_service.py # 邮件发送服务
├── responses/ # 响应格式
│ ├── __init__.py
│ └── HertzResponse.py # 统一响应类
└── validators/ # 验证器
├── __init__.py
├── email_validator.py # 邮箱验证器
├── password_validator.py # 密码验证器
└── phone_validator.py # 手机号验证器
```
## 核心类说明
### 1. EncryptionUtils 加密工具类
提供多种加密算法和工具函数:
```python
from hertz_studio_django_utils.crypto import EncryptionUtils
# MD5哈希
hash_value = EncryptionUtils.md5_hash("password", "salt")
# SHA256哈希
hash_value = EncryptionUtils.sha256_hash("password", "salt")
# 数据加密解密
encrypted = EncryptionUtils.encrypt_data("敏感数据", "password")
decrypted = EncryptionUtils.decrypt_data(encrypted, "password")
# 生成随机盐值
salt = EncryptionUtils.generate_salt(32)
```
### 2. MD5PasswordHasher MD5密码哈希器
兼容旧系统的MD5密码加密
```python
from hertz_studio_django_utils.crypto import MD5PasswordHasher
hasher = MD5PasswordHasher()
encoded_password = hasher.encode("password", "salt")
is_valid = hasher.verify("password", encoded_password)
```
### 3. EmailService 邮件服务类
提供邮件发送功能:
```python
from hertz_studio_django_utils.email import EmailService
# 发送普通邮件
success = EmailService.send_email(
recipient_email="user@example.com",
subject="邮件主题",
html_content="<h1>HTML内容</h1>",
text_content="纯文本内容"
)
# 发送验证码邮件
success = EmailService.send_verification_code(
recipient_email="user@example.com",
recipient_name="用户名",
verification_code="123456",
code_type="register"
)
```
### 4. HertzResponse 统一响应类
标准化的API响应格式
```python
from hertz_studio_django_utils.responses import HertzResponse
# 成功响应
return HertzResponse.success(data={"user": user_data}, message="操作成功")
# 失败响应
return HertzResponse.fail(message="操作失败", data={"error": "详情"})
# 错误响应
return HertzResponse.error(message="系统错误", error=str(e))
# 验证错误
return HertzResponse.validation_error(message="参数错误", errors=serializer.errors)
# 自定义响应
return HertzResponse.custom(
success=True,
message="自定义消息",
data={"custom": "data"},
code=200
)
```
### 5. 验证器类
提供数据格式验证功能:
```python
from hertz_studio_django_utils.validators import (
EmailValidator, PasswordValidator, PhoneValidator
)
# 邮箱验证
is_valid, message = EmailValidator.validate_email("test@example.com")
normalized_email = EmailValidator.normalize_email(" Test@Example.COM ")
# 密码强度验证
is_valid, errors = PasswordValidator.validate_password_strength("Password123!")
score = PasswordValidator.calculate_password_score("Password123!")
level = PasswordValidator.get_password_strength_level("Password123!")
# 手机号验证
is_valid = PhoneValidator.is_valid_china_mobile("13800138000")
is_valid, message = PhoneValidator.validate_china_mobile("13800138000")
carrier = PhoneValidator.get_mobile_carrier("13800138000")
```
## 安装和配置
### 1. 依赖安装
确保已安装以下依赖:
```bash
pip install Django>=5.2.6
pip install cryptography>=41.0.0
```
### 2. 配置邮件服务
`settings.py` 中配置邮件服务:
```python
# 邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@example.com'
EMAIL_HOST_PASSWORD = 'your-password'
DEFAULT_FROM_EMAIL = 'noreply@example.com'
```
### 3. 配置密码哈希器
`settings.py` 中配置密码哈希器:
```python
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'hertz_studio_django_utils.crypto.password_hashers.MD5PasswordHasher', # MD5兼容
]
```
## 快速开始
### 1. 导入模块
```python
# 导入整个工具模块
from hertz_studio_django_utils import (
EncryptionUtils,
MD5PasswordHasher,
EmailService,
HertzResponse,
EmailValidator,
PasswordValidator,
PhoneValidator
)
# 或按需导入特定功能
from hertz_studio_django_utils.responses import HertzResponse
from hertz_studio_django_utils.email import EmailService
```
### 2. 使用示例
```python
# 在Django视图中使用统一响应
from rest_framework.decorators import api_view
from hertz_studio_django_utils.responses import HertzResponse
@api_view(['GET'])
def user_profile(request):
try:
user_data = {"username": "testuser", "email": "test@example.com"}
return HertzResponse.success(data=user_data, message="获取用户信息成功")
except Exception as e:
return HertzResponse.error(message="获取用户信息失败", error=str(e))
# 发送验证码邮件
from hertz_studio_django_utils.email import EmailService
success = EmailService.send_verification_code(
recipient_email="user@example.com",
recipient_name="张三",
verification_code="654321",
code_type="register"
)
if success:
print("验证码邮件发送成功")
else:
print("验证码邮件发送失败")
```
## API接口
### 加密工具 API
| 方法 | 描述 | 参数 | 返回值 |
|------|------|------|--------|
| `md5_hash(data, salt)` | MD5哈希加密 | data: str, salt: str | str |
| `sha256_hash(data, salt)` | SHA256哈希加密 | data: str, salt: str | str |
| `encrypt_data(data, password)` | 加密数据 | data: str, password: str | Optional[str] |
| `decrypt_data(encrypted_data, password)` | 解密数据 | encrypted_data: str, password: str | Optional[str] |
| `generate_salt(length)` | 生成随机盐值 | length: int | str |
### 邮件服务 API
| 方法 | 描述 | 参数 | 返回值 |
|------|------|------|--------|
| `send_email()` | 发送邮件 | recipient_email, subject, html_content, text_content, from_email | bool |
| `send_verification_code()` | 发送验证码邮件 | recipient_email, recipient_name, verification_code, code_type | bool |
### 响应格式 API
| 方法 | 描述 | 参数 | 返回值 |
|------|------|------|--------|
| `success()` | 成功响应 | data, message, code | JsonResponse |
| `fail()` | 失败响应 | message, data, code | JsonResponse |
| `error()` | 错误响应 | message, error, code | JsonResponse |
| `unauthorized()` | 未授权响应 | message, code | JsonResponse |
| `validation_error()` | 验证错误响应 | message, errors, code | JsonResponse |
| `custom()` | 自定义响应 | success, message, data, code, **kwargs | JsonResponse |
### 验证器 API
| 类 | 方法 | 描述 |
|----|------|------|
| `EmailValidator` | `is_valid_email()` | 验证邮箱格式 |
| | `validate_email()` | 验证邮箱并返回详细信息 |
| | `normalize_email()` | 标准化邮箱地址 |
| `PasswordValidator` | `validate_password_strength()` | 验证密码强度 |
| | `calculate_password_score()` | 计算密码强度分数 |
| | `get_password_strength_level()` | 获取密码强度等级 |
| `PhoneValidator` | `is_valid_china_mobile()` | 验证中国大陆手机号 |
| | `validate_china_mobile()` | 验证手机号并返回详细信息 |
| | `normalize_phone()` | 标准化手机号 |
## 配置参数
### 邮件服务配置
| 参数 | 默认值 | 描述 |
|------|--------|------|
| `EMAIL_HOST` | - | SMTP服务器地址 |
| `EMAIL_PORT` | 587 | SMTP端口 |
| `EMAIL_USE_TLS` | True | 使用TLS加密 |
| `EMAIL_HOST_USER` | - | SMTP用户名 |
| `EMAIL_HOST_PASSWORD` | - | SMTP密码 |
| `DEFAULT_FROM_EMAIL` | - | 默认发件人邮箱 |
### 密码验证配置
| 参数 | 默认值 | 描述 |
|------|--------|------|
| `min_length` | 8 | 密码最小长度 |
| `max_length` | 128 | 密码最大长度 |
## 高级用法
### 自定义邮件模板
```python
from hertz_studio_django_utils.email import EmailService
# 自定义邮件内容
custom_html = """
<html>
<body>
<h1>自定义邮件</h1>
<p>您好 {name},这是一封自定义邮件。</p>
</body>
</html>
""".format(name="张三")
success = EmailService.send_email(
recipient_email="user@example.com",
subject="自定义邮件",
html_content=custom_html
)
```
### 扩展响应格式
```python
from hertz_studio_django_utils.responses import HertzResponse
# 扩展自定义响应
class CustomResponse(HertzResponse):
@staticmethod
def paginated(data, total, page, page_size, message="查询成功"):
"""分页响应"""
pagination = {
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
return HertzResponse.success(
data={"items": data, "pagination": pagination},
message=message
)
# 使用扩展响应
return CustomResponse.paginated(
data=users,
total=100,
page=1,
page_size=20,
message="用户列表查询成功"
)
```
## 测试
### 单元测试示例
```python
from django.test import TestCase
from hertz_studio_django_utils.validators import EmailValidator
class EmailValidatorTest(TestCase):
def test_valid_email(self):
"""测试有效邮箱地址"""
is_valid, message = EmailValidator.validate_email("test@example.com")
self.assertTrue(is_valid)
self.assertEqual(message, "邮箱地址格式正确")
def test_invalid_email(self):
"""测试无效邮箱地址"""
is_valid, message = EmailValidator.validate_email("invalid-email")
self.assertFalse(is_valid)
self.assertEqual(message, "邮箱地址格式不正确")
```
### 运行测试
```bash
python manage.py test hertz_studio_django_utils.tests
```
## 安全考虑
1. **密码安全**: 使用强密码哈希算法,避免明文存储密码
2. **加密安全**: 使用安全的加密算法和随机盐值
3. **输入验证**: 对所有输入数据进行严格验证
4. **错误处理**: 避免泄露敏感错误信息
5. **邮件安全**: 使用TLS加密邮件传输
## 常见问题
### Q: 邮件发送失败怎么办?
A: 检查邮件配置是否正确包括SMTP服务器、端口、用户名和密码。
### Q: MD5密码哈希是否安全
A: MD5被认为是不安全的哈希算法仅用于兼容旧系统。新系统应使用更安全的算法如bcrypt或Argon2。
### Q: 如何自定义响应格式?
A: 可以继承 `HertzResponse` 类并添加自定义方法。
### Q: 验证器是否支持国际手机号?
A: 目前主要支持中国大陆手机号验证,国际手机号验证功能有限。
## 更新日志
### v1.0.0 (2024-01-01)
- 初始版本发布
- 包含加密工具、邮件服务、响应格式、验证器等核心功能
## 🔗 相关链接
- [🏠 返回主项目](../README.md) - Hertz Server Django 主项目
- [🔐 认证授权模块](../hertz_studio_django_auth/README.md) - 用户管理和权限控制
- [🖼️ 验证码模块](../hertz_studio_django_captcha/README.md) - 图片和邮箱验证码功能
- [📋 代码风格指南](../CODING_STYLE_GUIDE.md) - 开发规范和最佳实践
- [Django 官方文档](https://docs.djangoproject.com/)
- [Django REST Framework 文档](https://www.django-rest-framework.org/)
## 贡献指南
1. Fork 本项目
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 打开 Pull Request
## 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
## 支持
如有问题或建议,请提交 [Issue](https://github.com/your-org/hertz-server-django/issues) 或联系开发团队。

View File

@@ -0,0 +1 @@
# Hertz Server Django Utils Package

View File

@@ -0,0 +1,39 @@
"""
Django代码生成器模块
该模块提供了Django应用代码自动生成功能包括
- 模型(ORM)代码生成
- 序列化器代码生成
- 视图(CRUD)代码生成
- URL路由代码生成
使用示例:
from hertz_studio_django_utils.code_generator import DjangoCodeGenerator
generator = DjangoCodeGenerator()
generator.generate_full_module(
model_name='User',
fields=[
{'name': 'username', 'type': 'CharField', 'max_length': 150},
{'name': 'email', 'type': 'EmailField'}
]
)
"""
from .base_generator import BaseGenerator
from .django_code_generator import DjangoCodeGenerator
from .model_generator import ModelGenerator
from .serializer_generator import SerializerGenerator
from .view_generator import ViewGenerator
from .url_generator import URLGenerator
from .menu_generator import MenuGenerator
__all__ = [
'BaseGenerator',
'DjangoCodeGenerator',
'ModelGenerator',
'SerializerGenerator',
'ViewGenerator',
'URLGenerator',
'MenuGenerator'
]

View File

@@ -0,0 +1,286 @@
"""
Django应用代码生成器
该模块负责根据配置生成完整的Django应用代码
"""
import os
from typing import Dict, List, Any, Optional
from .base_generator import BaseGenerator
from .model_generator import ModelGenerator
from .serializer_generator import SerializerGenerator
from .view_generator import ViewGenerator
from .url_generator import URLGenerator
class AppGenerator(BaseGenerator):
"""Django应用代码生成器"""
def __init__(self):
"""初始化应用生成器"""
super().__init__()
self.model_generator = ModelGenerator()
self.serializer_generator = SerializerGenerator()
self.view_generator = ViewGenerator()
self.url_generator = URLGenerator()
def generate(self, app_name: str = None, models: List[Dict[str, Any]] = None, **kwargs) -> Dict[str, str]:
"""
生成完整的Django应用代码
Args:
app_name: 应用名称
models: 模型配置列表
**kwargs: 其他参数
Returns:
Dict[str, str]: 生成的文件代码映射
"""
# 处理参数
if app_name is None:
app_name = kwargs.get('app_name', 'default_app')
if models is None:
models = kwargs.get('models', [])
api_version = kwargs.get('api_version', 'v1')
include_admin = kwargs.get('include_admin', True)
include_tests = kwargs.get('include_tests', True)
generated_files = {}
# 生成应用配置文件
generated_files['apps.py'] = self.generate_apps_config(app_name)
# 生成__init__.py文件
generated_files['__init__.py'] = self.generate_init_file(app_name)
# 生成模型、序列化器、视图和URL
all_models = []
for model_config in models:
model_name = model_config.get('name', 'DefaultModel')
fields = model_config.get('fields', [])
operations = model_config.get('operations', ['list', 'create', 'retrieve', 'update', 'delete'])
# 生成模型代码
model_code = self.model_generator.generate(
model_name=model_name,
fields=fields,
**model_config
)
# 生成序列化器代码
serializer_code = self.serializer_generator.generate(
model_name=model_name,
fields=fields
)
# 生成视图代码
view_code = self.view_generator.generate(
model_name=model_name,
operations=operations
)
# 生成URL代码
url_code = self.url_generator.generate(
model_name=model_name,
operations=operations,
app_name=app_name
)
# 添加到生成的文件中
generated_files[f'models/{model_name.lower()}_models.py'] = model_code
generated_files[f'serializers/{model_name.lower()}_serializers.py'] = serializer_code
generated_files[f'views/{model_name.lower()}_views.py'] = view_code
generated_files[f'urls/{model_name.lower()}_urls.py'] = url_code
all_models.append(model_name)
# 生成主要文件
generated_files['models.py'] = self.generate_models_init(all_models)
generated_files['serializers/__init__.py'] = self.generate_serializers_init(all_models)
generated_files['views/__init__.py'] = self.generate_views_init(all_models)
generated_files['urls.py'] = self.generate_main_urls(app_name, all_models, api_version)
# 生成管理后台文件
if include_admin:
generated_files['admin.py'] = self.generate_admin_config(all_models)
# 生成测试文件
if include_tests:
generated_files['tests.py'] = self.generate_tests(all_models)
return generated_files
def generate_apps_config(self, app_name: str) -> str:
"""生成应用配置文件"""
context = {
'app_name': app_name,
'app_label': app_name.replace('hertz_studio_django_', ''),
'verbose_name': app_name.replace('_', ' ').title()
}
return self.render_template('django/apps.mako', context)
def generate_init_file(self, app_name: str) -> str:
"""生成__init__.py文件"""
return f'"""\n{app_name} Django应用\n"""\n\ndefault_app_config = \'{app_name}.apps.{app_name.title().replace("_", "")}Config\'\n'
def generate_models_init(self, models: List[str]) -> str:
"""生成models.py主文件"""
imports = []
for model in models:
imports.append(f'from .models.{model.lower()}_models import {model}')
context = {
'imports': imports,
'models': models
}
return self.render_template('django/models_init.mako', context)
def generate_serializers_init(self, models: List[str]) -> str:
"""生成serializers/__init__.py文件"""
imports = []
for model in models:
imports.append(f'from .{model.lower()}_serializers import {model}Serializer')
return '\n'.join(imports) + '\n'
def generate_views_init(self, models: List[str]) -> str:
"""生成views/__init__.py文件"""
imports = []
for model in models:
imports.append(f'from .{model.lower()}_views import {model}ViewSet')
return '\n'.join(imports) + '\n'
def generate_main_urls(self, app_name: str, models: List[str], api_version: str) -> str:
"""生成主URL配置文件"""
context = {
'app_name': app_name,
'models': models,
'api_version': api_version,
'url_includes': [f'path(\'{model.lower()}/\', include(\'{app_name}.urls.{model.lower()}_urls\'))' for model in models]
}
return self.render_template('django/main_urls.mako', context)
def generate_admin_config(self, models: List[str]) -> str:
"""生成管理后台配置"""
context = {
'models': models
}
return self.render_template('django/admin.mako', context)
def generate_tests(self, models: List[str]) -> str:
"""生成测试文件"""
context = {
'models': models
}
return self.render_template('django/tests.mako', context)
def generate_full_app(
self,
app_name: str,
models: List[Dict[str, Any]],
output_dir: str = None,
**kwargs
) -> Dict[str, str]:
"""
生成完整的Django应用并写入文件
Args:
app_name: 应用名称
models: 模型配置列表
output_dir: 输出目录
**kwargs: 其他参数
Returns:
Dict[str, str]: 生成的文件路径映射
"""
generated_files = self.generate(app_name, models, **kwargs)
if output_dir:
file_paths = {}
for file_name, content in generated_files.items():
file_path = os.path.join(output_dir, app_name, file_name)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
file_paths[file_name] = file_path
return file_paths
return generated_files
def generate_django_project(
self,
project_name: str,
apps: List[Dict[str, Any]],
output_dir: str = None,
**kwargs
) -> Dict[str, str]:
"""
生成完整的Django项目
Args:
project_name: 项目名称
apps: 应用配置列表
output_dir: 输出目录
**kwargs: 其他参数
Returns:
Dict[str, str]: 生成的文件路径映射
"""
generated_files = {}
# 生成项目配置文件
generated_files['manage.py'] = self.generate_manage_py(project_name)
generated_files[f'{project_name}/settings.py'] = self.generate_settings(project_name, apps)
generated_files[f'{project_name}/urls.py'] = self.generate_project_urls(project_name, apps)
generated_files[f'{project_name}/wsgi.py'] = self.generate_wsgi(project_name)
generated_files[f'{project_name}/asgi.py'] = self.generate_asgi(project_name)
generated_files[f'{project_name}/__init__.py'] = ''
# 生成每个应用
for app_config in apps:
app_name = app_config.get('name', 'default_app')
models = app_config.get('models', [])
app_files = self.generate(app_name, models, **app_config)
for file_name, content in app_files.items():
generated_files[f'{app_name}/{file_name}'] = content
return generated_files
def generate_manage_py(self, project_name: str) -> str:
"""生成manage.py文件"""
context = {'project_name': project_name}
return self.render_template('django/manage.mako', context)
def generate_settings(self, project_name: str, apps: List[Dict[str, Any]]) -> str:
"""生成settings.py文件"""
app_names = [app.get('name', 'default_app') for app in apps]
context = {
'project_name': project_name,
'apps': app_names
}
return self.render_template('django/settings.mako', context)
def generate_project_urls(self, project_name: str, apps: List[Dict[str, Any]]) -> str:
"""生成项目主URL配置"""
app_names = [app.get('name', 'default_app') for app in apps]
context = {
'project_name': project_name,
'apps': app_names
}
return self.render_template('django/project_urls.mako', context)
def generate_wsgi(self, project_name: str) -> str:
"""生成WSGI配置"""
context = {'project_name': project_name}
return self.render_template('django/wsgi.mako', context)
def generate_asgi(self, project_name: str) -> str:
"""生成ASGI配置"""
context = {'project_name': project_name}
return self.render_template('django/asgi.mako', context)

View File

@@ -0,0 +1,262 @@
"""
基础代码生成器类
提供代码生成的基础功能和通用方法
"""
import os
import re
from typing import Dict, List, Any, Optional
from abc import ABC, abstractmethod
from .template_engine import TemplateEngine
class BaseGenerator(ABC):
"""
基础代码生成器抽象类
"""
def __init__(self, template_dir: str = None):
"""
初始化基础生成器
Args:
template_dir: 模板目录路径
"""
self.template_vars = {}
self.template_engine = TemplateEngine(template_dir)
def render_template(self, template_name: str, context: Dict[str, Any] = None) -> str:
"""
渲染模板
Args:
template_name: 模板文件名
context: 模板上下文变量
Returns:
str: 渲染后的代码字符串
"""
if context is None:
context = {}
# 合并模板变量和上下文
merged_context = {**self.template_vars, **context}
return self.template_engine.render_template(template_name, merged_context)
def create_template_context(self, **kwargs) -> Dict[str, Any]:
"""
创建模板上下文
Args:
**kwargs: 上下文变量
Returns:
Dict[str, Any]: 模板上下文
"""
context = {**self.template_vars, **kwargs}
# 添加常用的辅助函数到上下文
context.update({
'snake_to_camel': self.snake_to_camel,
'camel_to_snake': self.camel_to_snake,
'format_field_name': self.format_field_name,
'format_class_name': self.format_class_name,
'format_verbose_name': self.format_verbose_name,
'get_django_field_type': self.get_django_field_type,
})
return context
@abstractmethod
def generate(self, **kwargs) -> str:
"""
生成代码的抽象方法
Returns:
str: 生成的代码字符串
"""
pass
def set_template_var(self, key: str, value: Any) -> None:
"""
设置模板变量
Args:
key: 变量名
value: 变量值
"""
self.template_vars[key] = value
def get_template_var(self, key: str, default: Any = None) -> Any:
"""
获取模板变量
Args:
key: 变量名
default: 默认值
Returns:
Any: 变量值
"""
return self.template_vars.get(key, default)
def snake_to_camel(self, snake_str: str) -> str:
"""
将蛇形命名转换为驼峰命名
Args:
snake_str: 蛇形命名字符串
Returns:
str: 驼峰命名字符串
"""
components = snake_str.split('_')
return ''.join(word.capitalize() for word in components)
def camel_to_snake(self, camel_str: str) -> str:
"""
将驼峰命名转换为蛇形命名
Args:
camel_str: 驼峰命名字符串
Returns:
str: 蛇形命名字符串
"""
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def format_field_name(self, field_name: str) -> str:
"""
格式化字段名称
Args:
field_name: 原始字段名
Returns:
str: 格式化后的字段名
"""
return field_name.lower().replace(' ', '_').replace('-', '_')
def format_class_name(self, name: str) -> str:
"""
格式化类名
Args:
name: 原始名称
Returns:
str: 格式化后的类名
"""
return self.snake_to_camel(self.format_field_name(name))
def format_verbose_name(self, field_name: str) -> str:
"""
格式化verbose_name
Args:
field_name: 字段名
Returns:
str: 格式化后的verbose_name
"""
return field_name.replace('_', ' ').title()
def generate_imports(self, imports: List[str]) -> str:
"""
生成导入语句
Args:
imports: 导入列表
Returns:
str: 导入语句字符串
"""
if not imports:
return ''
return '\n'.join(imports) + '\n\n'
def indent_code(self, code: str, indent_level: int = 1) -> str:
"""
为代码添加缩进
Args:
code: 代码字符串
indent_level: 缩进级别
Returns:
str: 缩进后的代码
"""
indent = ' ' * indent_level
lines = code.split('\n')
return '\n'.join(indent + line if line.strip() else line for line in lines)
def write_to_file(self, file_path: str, content: str) -> bool:
"""
将内容写入文件
Args:
file_path: 文件路径
content: 文件内容
Returns:
bool: 是否写入成功
"""
try:
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception as e:
print(f"写入文件失败: {e}")
return False
def validate_field_config(self, field_config: Dict[str, Any]) -> bool:
"""
验证字段配置
Args:
field_config: 字段配置字典
Returns:
bool: 配置是否有效
"""
required_keys = ['name', 'type']
return all(key in field_config for key in required_keys)
def get_django_field_type(self, field_type: str) -> str:
"""
获取Django字段类型
Args:
field_type: 字段类型
Returns:
str: Django字段类型
"""
field_mapping = {
'string': 'CharField',
'text': 'TextField',
'integer': 'IntegerField',
'float': 'FloatField',
'decimal': 'DecimalField',
'boolean': 'BooleanField',
'date': 'DateField',
'datetime': 'DateTimeField',
'time': 'TimeField',
'email': 'EmailField',
'url': 'URLField',
'file': 'FileField',
'image': 'ImageField',
'json': 'JSONField',
'uuid': 'UUIDField',
'foreign_key': 'ForeignKey',
'many_to_many': 'ManyToManyField',
'one_to_one': 'OneToOneField'
}
return field_mapping.get(field_type.lower(), 'CharField')

View File

@@ -0,0 +1,401 @@
# Hertz Server Django 主项目配置
## 📋 项目概述
Hertz Server Django 是一个基于 Django 和 Django REST Framework 构建的现代化后端服务框架,提供认证授权、验证码、工具类等核心功能模块。主项目配置模块负责整个项目的全局配置、路由管理和基础设置。
## ✨ 核心特性
- **模块化架构**: 采用微服务架构设计,各功能模块独立开发部署
- **RESTful API**: 基于DRF构建的标准REST API接口
- **OpenAPI 3.0文档**: 自动生成的API文档支持Swagger UI和ReDoc
- **多数据库支持**: 支持SQLite和MySQL可配置Redis作为缓存和会话存储
- **跨域支持**: 内置CORS跨域请求处理
- **WebSocket支持**: 基于Channels的实时通信能力
- **环境配置**: 使用python-decouple进行环境变量管理
## 📁 项目结构
```
hertz_server_django/ # 项目根目录
├── hertz_server_django/ # 主项目配置模块
│ ├── __init__.py # 包初始化文件
│ ├── settings.py # 项目全局配置
│ ├── urls.py # 主URL路由配置
│ ├── asgi.py # ASGI应用配置
│ ├── wsgi.py # WSGI应用配置
│ └── views.py # 根视图函数
├── hertz_demo/ # 演示模块
├── hertz_studio_django_captcha/ # 验证码模块
├── hertz_studio_django_auth/ # 认证授权模块
├── hertz_studio_django_utils/ # 工具类模块
├── manage.py # Django管理脚本
├── requirements.txt # 项目依赖
├── .env # 环境变量配置
└── data/ # 数据目录SQLite数据库等
```
## ⚙️ 核心配置文件
### settings.py - 项目全局配置
#### 基础配置
```python
# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent
# 安全密钥(从环境变量读取)
SECRET_KEY = config('SECRET_KEY', default='django-insecure-...')
# 调试模式
DEBUG = config('DEBUG', default=True, cast=bool)
# 允许的主机
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1')
```
#### 应用配置
```python
INSTALLED_APPS = [
# Django核心应用
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用
'rest_framework', # Django REST Framework
'corsheaders', # CORS跨域支持
'channels', # WebSocket支持
'drf_spectacular', # OpenAPI文档生成
# 本地应用模块
'hertz_demo', # 演示模块
'hertz_studio_django_captcha', # 验证码模块
'hertz_studio_django_auth', # 认证授权模块
]
```
#### 数据库配置
```python
# 数据库切换配置
USE_REDIS_AS_DB = config('USE_REDIS_AS_DB', default=True, cast=bool)
if USE_REDIS_AS_DB:
# 开发环境使用SQLite
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
else:
# 生产环境使用MySQL
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME', default='hertz_server'),
'USER': config('DB_USER', default='root'),
'PASSWORD': config('DB_PASSWORD', default='root'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='3306'),
'OPTIONS': {'charset': 'utf8mb4'},
}
}
```
#### Redis缓存配置
```python
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/0'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
```
#### DRF配置
```python
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': [],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
```
#### OpenAPI文档配置
```python
SPECTACULAR_SETTINGS = {
'TITLE': 'Hertz Server API',
'DESCRIPTION': 'API documentation for Hertz Server Django project',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SCHEMA_PATH_PREFIX': '/api/',
}
```
### urls.py - 主路由配置
```python
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from . import views
urlpatterns = [
# 首页路由
path('', views.index, name='index'),
# Hertz Captcha路由
path('api/captcha/', include('hertz_studio_django_captcha.urls')),
# Hertz Auth路由
path('api/', include('hertz_studio_django_auth.urls')),
# Demo应用路由
path('', include('hertz_demo.urls')),
# API文档
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
]
```
## 🚀 快速开始
### 1. 环境准备
```bash
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
```
### 2. 环境配置
创建 `.env` 文件:
```ini
# 基础配置
DEBUG=True
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=localhost,127.0.0.1
# 数据库配置
USE_REDIS_AS_DB=True
REDIS_URL=redis://127.0.0.1:6379/0
# MySQL配置如果USE_REDIS_AS_DB=False
DB_NAME=hertz_server
DB_USER=root
DB_PASSWORD=root
DB_HOST=localhost
DB_PORT=3306
```
### 3. 数据库初始化
```bash
# 创建数据库迁移
python manage.py makemigrations
# 应用数据库迁移
python manage.py migrate
# 创建超级用户(可选)
python manage.py createsuperuser
```
### 4. 启动开发服务器
```bash
# 启动Django开发服务器
python manage.py runserver
# 访问应用
# 首页: http://localhost:8000/
# API文档: http://localhost:8000/api/docs/
# 演示页面: http://localhost:8000/demo/captcha/
```
## 🔧 配置详解
### 环境变量管理
项目使用 `python-decouple` 进行环境变量管理,支持:
-`.env` 文件读取配置
- 类型转换和默认值设置
- 开发和生产环境分离
### 数据库配置策略
**开发环境**: 使用SQLite + Redis缓存
- 快速启动和开发测试
- 数据存储在SQLite文件
- 会话和缓存使用Redis
**生产环境**: 使用MySQL + Redis缓存
- 高性能数据库支持
- 数据持久化存储
- Redis用于缓存和会话
### 中间件配置
```python
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # CORS处理
'django.middleware.security.SecurityMiddleware', # 安全中间件
'django.contrib.sessions.middleware.SessionMiddleware', # 会话管理
'django.middleware.common.CommonMiddleware', # 通用处理
'django.middleware.csrf.CsrfViewMiddleware', # CSRF保护
'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证
'hertz_studio_django_auth.utils.middleware.AuthMiddleware', # 自定义认证
'django.contrib.messages.middleware.MessageMiddleware', # 消息框架
'django.middleware.clickjacking.XFrameOptionsMiddleware', # 点击劫持保护
]
```
## 🌐 API文档访问
项目提供完整的OpenAPI 3.0文档:
- **Swagger UI**: http://localhost:8000/api/docs/
- **ReDoc**: http://localhost:8000/api/redoc/
- **Schema JSON**: http://localhost:8000/api/schema/
## 🚢 部署配置
### 生产环境部署
1. **环境变量配置**:
```ini
DEBUG=False
SECRET_KEY=your-production-secret-key
ALLOWED_HOSTS=your-domain.com,api.your-domain.com
USE_REDIS_AS_DB=False
```
2. **静态文件收集**:
```bash
python manage.py collectstatic
```
3. **WSGI部署**:
```python
# 使用Gunicorn + Nginx
# gunicorn hertz_server_django.wsgi:application
```
4. **ASGI部署**:
```python
# 使用Daphne + Nginx
# daphne hertz_server_django.asgi:application
```
## 🔒 安全配置
### 生产环境安全设置
```python
# 强制HTTPS
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# CSRF保护
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
# 会话安全
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
# 安全头部
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
```
## 📊 性能优化
### 缓存策略
```python
# 数据库查询缓存
from django.core.cache import cache
# 使用缓存
cache.set('key', 'value', timeout=300)
value = cache.get('key')
```
### 数据库优化
```python
# 使用select_related减少查询
users = User.objects.select_related('profile').filter(is_active=True)
# 使用prefetch_related优化多对多关系
users = User.objects.prefetch_related('groups', 'permissions')
```
## 🐛 故障排除
### 常见问题
1. **Redis连接失败**: 检查Redis服务是否启动配置是否正确
2. **数据库迁移错误**: 删除数据库文件重新迁移或检查MySQL连接
3. **静态文件404**: 运行 `python manage.py collectstatic`
4. **CORS问题**: 检查CORS配置和中间件顺序
### 日志配置
```python
# settings.py 中添加日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
}
```
## 🔗 相关链接
- [🎮 演示模块](../hertz_demo/README.md) - 功能演示和测试页面
- [🔐 认证授权模块](../hertz_studio_django_auth/README.md) - 用户管理和权限控制
- [📸 验证码模块](../hertz_studio_django_captcha/README.md) - 验证码生成和验证
- [🛠️ 工具类模块](../hertz_studio_django_utils/README.md) - 加密、邮件和验证工具
- [🐍 Django文档](https://docs.djangoproject.com/) - Django官方文档
- [🔌 DRF文档](https://www.django-rest-framework.org/) - Django REST Framework文档
---
💡 **提示**: 此配置模块是整个项目的核心,负责协调各功能模块的协同工作。生产部署前请务必检查所有安全配置。

View File

@@ -0,0 +1,35 @@
"""
ASGI config for hertz_server_django project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
from hertz_demo import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
# Import websocket routing
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
routing.websocket_urlpatterns
)
)
),
})

View File

@@ -0,0 +1,335 @@
"""
Django settings for hertz_server_django project.
Generated by 'django-admin startproject' using Django 5.2.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from pathlib import Path
from decouple import config
# 修复DRF的ip_address_validators函数
def fix_drf_ip_validators():
"""
修复DRF的ip_address_validators函数返回值问题
"""
try:
from rest_framework import fields
# 保存原始函数
original_ip_address_validators = fields.ip_address_validators
def fixed_ip_address_validators(protocol, unpack_ipv4):
"""
修复后的ip_address_validators函数确保返回两个值
"""
validators = original_ip_address_validators(protocol, unpack_ipv4)
# 如果只返回了validators添加默认的error_message
if isinstance(validators, list):
return validators, 'Enter a valid IP address.'
else:
# 如果已经返回了两个值,直接返回
return validators
# 应用猴子补丁
fields.ip_address_validators = fixed_ip_address_validators
except ImportError:
# 如果DRF未安装忽略错误
pass
# 应用修复
fix_drf_ip_validators()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
# Database switch configuration
USE_REDIS_AS_DB = config('USE_REDIS_AS_DB', default=True, cast=bool)
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third party apps
'rest_framework',
'corsheaders',
'channels',
'drf_spectacular',
# Local apps
'hertz_demo',
'hertz_studio_django_captcha',
'hertz_studio_django_auth', # 权限管理系统
'hertz_studio_django_notice', # 通知公告模块
'hertz_studio_django_ai',
'hertz_studio_django_system_monitor', # 系统监测模块
'hertz_studio_django_log', # 日志管理模块
'hertz_studio_django_wiki', # 知识管理模块
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'hertz_studio_django_auth.utils.middleware.AuthMiddleware', # 权限认证中间件
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'hertz_server_django.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'hertz_server_django.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
if USE_REDIS_AS_DB:
# Redis as primary database (for caching and session storage)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
# Use Redis for sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
else:
# MySQL database configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME', default='hertz_server'),
'USER': config('DB_USER', default='root'),
'PASSWORD': config('DB_PASSWORD', default='root'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='3306'),
'OPTIONS': {
'charset': 'utf8mb4',
},
}
}
# Redis
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/0'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# Media files (User uploaded files)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Django REST Framework configuration
# 使用自定义AuthMiddleware进行认证不使用DRF的认证和权限系统
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': [], # 不使用DRF认证类
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # 所有接口默认允许访问由AuthMiddleware控制权限
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
# Spectacular (OpenAPI 3.0) configuration
SPECTACULAR_SETTINGS = {
'TITLE': 'Hertz Server API',
'DESCRIPTION': 'API documentation for Hertz Server Django project',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SCHEMA_PATH_PREFIX': '/api/',
}
# CORS configuration
CORS_ALLOWED_ORIGINS = config(
'CORS_ALLOWED_ORIGINS',
default='http://localhost:3000,http://127.0.0.1:3000',
cast=lambda v: [s.strip() for s in v.split(',')]
)
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = config('CORS_ALLOW_ALL_ORIGINS', default=False, cast=bool)
# Captcha settings
CAPTCHA_IMAGE_SIZE = (
config('CAPTCHA_IMAGE_SIZE_WIDTH', default=120, cast=int),
config('CAPTCHA_IMAGE_SIZE_HEIGHT', default=50, cast=int)
)
CAPTCHA_LENGTH = config('CAPTCHA_LENGTH', default=4, cast=int)
CAPTCHA_TIMEOUT = config('CAPTCHA_TIMEOUT', default=5, cast=int) # minutes
CAPTCHA_FONT_SIZE = config('CAPTCHA_FONT_SIZE', default=40, cast=int)
CAPTCHA_BACKGROUND_COLOR = config('CAPTCHA_BACKGROUND_COLOR', default='#ffffff')
CAPTCHA_FOREGROUND_COLOR = config('CAPTCHA_FOREGROUND_COLOR', default='#000000')
# 验证码词典文件路径
CAPTCHA_WORDS_DICTIONARY = str(BASE_DIR / 'captcha_words.txt')
# 验证码挑战函数配置
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' # 默认使用随机字符
# 数学验证码配置
CAPTCHA_MATH_CHALLENGE_OPERATOR = '+-*'
# 验证码噪声和过滤器
CAPTCHA_NOISE_FUNCTIONS = (
'captcha.helpers.noise_arcs',
'captcha.helpers.noise_dots',
)
CAPTCHA_FILTER_FUNCTIONS = (
'captcha.helpers.post_smooth',
)
# Email configuration
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='smtp.qq.com')
EMAIL_PORT = config('EMAIL_PORT', default=465, cast=int)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=True, cast=bool)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='563161210@qq.com')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='563161210@qq.com')
# Channels configuration for WebSocket support
ASGI_APPLICATION = 'hertz_server_django.asgi.application'
# Channel layers configuration
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [config('REDIS_URL', default='redis://127.0.0.1:6379/2')],
},
},
}
# 自定义用户模型
AUTH_USER_MODEL = 'hertz_studio_django_auth.HertzUser'
# JWT配置
JWT_SECRET_KEY = config('JWT_SECRET_KEY', default=SECRET_KEY)
JWT_ALGORITHM = 'HS256'
JWT_ACCESS_TOKEN_LIFETIME = config('JWT_ACCESS_TOKEN_LIFETIME', default=60 * 60 * 24, cast=int) # 24小时
JWT_REFRESH_TOKEN_LIFETIME = config('JWT_REFRESH_TOKEN_LIFETIME', default=60 * 60 * 24 * 7, cast=int) # 7天
# 权限系统配置
HERTZ_AUTH_SETTINGS = {
'SUPER_ADMIN_PERMISSIONS': ['*'], # 超级管理员拥有所有权限
'DEFAULT_PERMISSIONS': [], # 默认权限
}
# AuthMiddleware配置 - 不需要登录验证的URL模式支持正则表达式
NO_AUTH_PATTERNS = config(
'NO_AUTH_PATTERNS',
default=r'^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$',
cast=lambda v: [s.strip() for s in v.split(',')]
)
# 密码加密配置
PASSWORD_HASHERS = [
'hertz_studio_django_utils.crypto.MD5PasswordHasher', # 使用MD5加密
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

View File

@@ -0,0 +1,62 @@
"""
URL configuration for hertz_server_django project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from . import views
urlpatterns = [
# 首页路由
path('', views.index, name='index'),
# Hertz Captcha routes
path('api/captcha/', include('hertz_studio_django_captcha.urls')),
# Hertz Auth routes
path('api/', include('hertz_studio_django_auth.urls')),
# Demo app routes
path('', include('hertz_demo.urls')),
# Hertz AI routes
path('api/ai/', include('hertz_studio_django_ai.urls')),
# Hertz System Monitor routes
path('api/system/', include('hertz_studio_django_system_monitor.urls')),
# Hertz System Notification routes
path('api/notice/', include('hertz_studio_django_notice.urls')),
# Hertz Log routes
path('api/log/', include('hertz_studio_django_log.urls')),
# Hertz Wiki routes
path('api/wiki/', include('hertz_studio_django_wiki.urls')),
# OpenAPI documentation
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
]
# 在开发环境下提供媒体文件服务
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0])

View File

@@ -0,0 +1,13 @@
from django.contrib.auth.models import Permission
from django.shortcuts import render
from hertz_studio_django_auth.utils.decorators import no_login_required
@no_login_required
def index(request):
"""
系统首页视图
展示系统的基础介绍和功能特性
"""
return render(request, 'index.html')

View File

@@ -0,0 +1,16 @@
"""
WSGI config for hertz_server_django project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
application = get_wsgi_application()

View File

@@ -0,0 +1,687 @@
"""
Django代码生成器主入口类
该模块提供了一个统一的Django代码生成器入口
整合了模型、序列化器、视图、URL路由等所有生成器功能。
使用示例:
generator = DjangoCodeGenerator()
# 生成完整的CRUD模块
generator.generate_full_module(
model_name='User',
fields=[
{'name': 'username', 'type': 'CharField', 'max_length': 150},
{'name': 'email', 'type': 'EmailField'},
{'name': 'phone', 'type': 'CharField', 'max_length': 20}
],
output_dir='./generated_code'
)
"""
import os
from typing import Dict, List, Optional, Any
from .base_generator import BaseGenerator
from .model_generator import ModelGenerator
from .serializer_generator import SerializerGenerator
from .view_generator import ViewGenerator
from .url_generator import URLGenerator
from .yaml_parser import YAMLParser
class DjangoCodeGenerator(BaseGenerator):
"""Django代码生成器主入口类"""
def __init__(self):
"""初始化Django代码生成器"""
super().__init__()
self.model_generator = ModelGenerator()
self.serializer_generator = SerializerGenerator()
self.view_generator = ViewGenerator()
self.url_generator = URLGenerator()
self.yaml_parser = YAMLParser()
def generate(self, **kwargs) -> str:
"""
实现抽象方法generate
Returns:
str: 生成的代码字符串
"""
return "Django代码生成器"
def generate_full_module(
self,
model_name: str,
fields: List[Dict[str, Any]],
output_dir: str = './generated_code',
app_name: str = None,
operations: List[str] = None,
permissions: List[str] = None,
validators: Optional[Dict[str, str]] = None,
table_name: str = None,
verbose_name: str = None,
ordering: List[str] = None
) -> Dict[str, str]:
"""
生成完整的Django模块代码模型、序列化器、视图、URL
Args:
model_name: 模型名称
fields: 字段配置列表
output_dir: 输出目录
app_name: 应用名称
operations: 支持的操作列表
permissions: 权限装饰器列表
validators: 字段验证器映射
table_name: 数据库表名
verbose_name: 模型显示名称
ordering: 默认排序字段
Returns:
Dict[str, str]: 生成的代码文件映射
"""
if operations is None:
operations = ['create', 'read', 'update', 'delete', 'list']
if not app_name:
app_name = self.to_snake_case(model_name)
generated_files = {}
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 生成模型代码
model_code = self.generate_model(
model_name=model_name,
fields=fields,
table_name=table_name,
verbose_name=verbose_name,
ordering=ordering
)
generated_files['models.py'] = model_code
# 生成序列化器代码
serializer_code = self.generate_serializers(
model_name=model_name,
fields=[field['name'] for field in fields],
validators=validators
)
generated_files['serializers.py'] = serializer_code
# 生成视图代码
view_code = self.generate_views(
model_name=model_name,
operations=operations,
permissions=permissions
)
generated_files['views.py'] = view_code
# 生成URL路由代码
url_code = self.generate_urls(
model_name=model_name,
operations=operations,
app_name=app_name
)
generated_files['urls.py'] = url_code
# 写入文件
for filename, code in generated_files.items():
file_path = os.path.join(output_dir, filename)
self.write_to_file(file_path, code)
return generated_files
def generate_model(
self,
model_name: str,
fields: List[Dict[str, Any]],
table_name: str = None,
verbose_name: str = None,
ordering: List[str] = None,
status_choices: List[tuple] = None
) -> str:
"""
生成模型代码
Args:
model_name: 模型名称
fields: 字段配置列表
table_name: 数据库表名
verbose_name: 模型显示名称
ordering: 默认排序字段
status_choices: 状态选择项
Returns:
str: 生成的模型代码
"""
return self.model_generator.generate(
model_name=model_name,
fields=fields,
table_name=table_name,
verbose_name=verbose_name,
ordering=ordering,
status_choices=status_choices
)
def generate_serializers(
self,
model_name: str,
fields: List[str],
validators: Optional[Dict[str, str]] = None,
create_fields: Optional[List[str]] = None,
update_fields: Optional[List[str]] = None,
list_fields: Optional[List[str]] = None
) -> str:
"""
生成序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
validators: 字段验证器映射
create_fields: 创建时使用的字段
update_fields: 更新时使用的字段
list_fields: 列表时显示的字段
Returns:
str: 生成的序列化器代码
"""
return self.serializer_generator.generate(
model_name=model_name,
fields=fields,
create_fields=create_fields,
update_fields=update_fields,
list_fields=list_fields,
validators=validators
)
def generate_views(
self,
model_name: str,
operations: List[str] = None,
permissions: List[str] = None,
filters: Optional[List[str]] = None,
ordering: Optional[List[str]] = None,
search_fields: Optional[List[str]] = None,
pagination: bool = True
) -> str:
"""
生成视图代码
Args:
model_name: 模型名称
operations: 支持的操作列表
permissions: 权限装饰器列表
filters: 过滤字段列表
ordering: 排序字段列表
search_fields: 搜索字段列表
pagination: 是否启用分页
Returns:
str: 生成的视图代码
"""
if operations is None:
operations = ['create', 'read', 'update', 'delete', 'list']
return self.view_generator.generate(
model_name=model_name,
operations=operations,
permissions=permissions,
filters=filters,
ordering=ordering,
search_fields=search_fields,
pagination=pagination
)
def generate_urls(
self,
model_name: str,
operations: List[str] = None,
prefix: str = '',
app_name: str = '',
namespace: str = ''
) -> str:
"""
生成URL路由代码
Args:
model_name: 模型名称
operations: 支持的操作列表
prefix: URL前缀
app_name: 应用名称
namespace: 命名空间
Returns:
str: 生成的URL路由代码
"""
if operations is None:
operations = ['create', 'read', 'update', 'delete', 'list']
return self.url_generator.generate(
model_name=model_name,
operations=operations,
prefix=prefix,
app_name=app_name,
namespace=namespace
)
def generate_api_module(
self,
model_name: str,
fields: List[Dict[str, Any]],
output_dir: str = './generated_api',
version: str = 'v1',
permissions: List[str] = None
) -> Dict[str, str]:
"""
生成RESTful API模块代码
Args:
model_name: 模型名称
fields: 字段配置列表
output_dir: 输出目录
version: API版本
permissions: 权限装饰器列表
Returns:
Dict[str, str]: 生成的代码文件映射
"""
generated_files = {}
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 生成模型代码
model_code = self.generate_model(
model_name=model_name,
fields=fields
)
generated_files['models.py'] = model_code
# 生成序列化器代码
serializer_code = self.generate_serializers(
model_name=model_name,
fields=[field['name'] for field in fields]
)
generated_files['serializers.py'] = serializer_code
# 生成API视图代码
view_code = self.generate_views(
model_name=model_name,
permissions=permissions
)
generated_files['views.py'] = view_code
# 生成API URL路由代码
url_code = self.url_generator.generate_api_urls(
model_name=model_name,
version=version
)
generated_files['urls.py'] = url_code
# 写入文件
for filename, code in generated_files.items():
file_path = os.path.join(output_dir, filename)
self.write_to_file(file_path, code)
return generated_files
def generate_nested_module(
self,
parent_model: str,
child_model: str,
parent_fields: List[Dict[str, Any]],
child_fields: List[Dict[str, Any]],
output_dir: str = './generated_nested',
relationship_type: str = 'foreign_key'
) -> Dict[str, str]:
"""
生成嵌套资源模块代码
Args:
parent_model: 父模型名称
child_model: 子模型名称
parent_fields: 父模型字段配置
child_fields: 子模型字段配置
output_dir: 输出目录
relationship_type: 关系类型 ('foreign_key', 'one_to_one', 'many_to_many')
Returns:
Dict[str, str]: 生成的代码文件映射
"""
generated_files = {}
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 在子模型字段中添加父模型关系
if relationship_type == 'foreign_key':
child_fields.append({
'name': self.to_snake_case(parent_model),
'type': 'ForeignKey',
'to': parent_model,
'on_delete': 'models.CASCADE',
'verbose_name': f'{parent_model}',
'help_text': f'关联的{parent_model}'
})
# 生成父模型代码
parent_model_code = self.generate_model(
model_name=parent_model,
fields=parent_fields
)
generated_files[f'{self.to_snake_case(parent_model)}_models.py'] = parent_model_code
# 生成子模型代码
child_model_code = self.generate_model(
model_name=child_model,
fields=child_fields
)
generated_files[f'{self.to_snake_case(child_model)}_models.py'] = child_model_code
# 生成嵌套URL路由
nested_url_code = self.url_generator.generate_nested_urls(
parent_model=parent_model,
child_model=child_model
)
generated_files['nested_urls.py'] = nested_url_code
# 写入文件
for filename, code in generated_files.items():
file_path = os.path.join(output_dir, filename)
self.write_to_file(file_path, code)
return generated_files
def generate_app_structure(
self,
app_name: str,
models: List[Dict[str, Any]],
output_dir: str = None
) -> Dict[str, str]:
"""
生成完整的Django应用结构
Args:
app_name: 应用名称
models: 模型配置列表
output_dir: 输出目录
Returns:
Dict[str, str]: 生成的代码文件映射
"""
if not output_dir:
output_dir = f'./generated_{app_name}'
generated_files = {}
# 创建应用目录结构
app_dir = os.path.join(output_dir, app_name)
os.makedirs(app_dir, exist_ok=True)
os.makedirs(os.path.join(app_dir, 'serializers'), exist_ok=True)
os.makedirs(os.path.join(app_dir, 'views'), exist_ok=True)
os.makedirs(os.path.join(app_dir, 'urls'), exist_ok=True)
# 生成应用配置文件
apps_code = self._generate_apps_config(app_name)
generated_files['apps.py'] = apps_code
# 生成__init__.py文件
generated_files['__init__.py'] = ''
generated_files['serializers/__init__.py'] = ''
generated_files['views/__init__.py'] = ''
generated_files['urls/__init__.py'] = ''
# 为每个模型生成代码
all_models_code = []
all_serializers_code = []
all_views_code = []
all_urls_code = []
for model_config in models:
model_name = model_config['name']
fields = model_config['fields']
# 生成模型代码
model_code = self.generate_model(
model_name=model_name,
fields=fields,
verbose_name=model_config.get('verbose_name', model_name),
table_name=model_config.get('table_name'),
ordering=model_config.get('ordering', ['-created_at'])
)
all_models_code.append(model_code)
# 生成序列化器代码
serializer_code = self.generate_serializers(
model_name=model_name,
fields=[field['name'] for field in fields],
validators=model_config.get('validators', {})
)
all_serializers_code.append(serializer_code)
# 生成视图代码
view_code = self.generate_views(
model_name=model_name,
operations=model_config.get('operations', ['create', 'read', 'update', 'delete', 'list']),
permissions=model_config.get('permissions', [])
)
all_views_code.append(view_code)
# 生成URL代码
url_code = self.generate_urls(
model_name=model_name,
app_name=app_name,
operations=model_config.get('operations', ['create', 'read', 'update', 'delete', 'list'])
)
all_urls_code.append(url_code)
# 合并所有代码
generated_files['models.py'] = '\n\n'.join(all_models_code)
generated_files['serializers.py'] = '\n\n'.join(all_serializers_code)
generated_files['views.py'] = '\n\n'.join(all_views_code)
generated_files['urls.py'] = '\n\n'.join(all_urls_code)
# 写入文件
for filename, code in generated_files.items():
file_path = os.path.join(app_dir, filename)
# 确保目录存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
self.write_to_file(file_path, code)
return generated_files
def _generate_apps_config(self, app_name: str) -> str:
"""生成Django应用配置代码"""
class_name = self.snake_to_camel(app_name) + 'Config'
code_parts = [
'from django.apps import AppConfig',
'',
f'class {class_name}(AppConfig):',
' """',
f' {app_name}应用配置',
' """',
" default_auto_field = 'django.db.models.BigAutoField'",
f" name = '{app_name}'",
f" verbose_name = '{app_name.title()}'"
]
return '\n'.join(code_parts)
def generate_from_yaml_file(self, yaml_file_path: str) -> Dict[str, str]:
"""
从YAML配置文件生成Django应用代码
Args:
yaml_file_path: YAML配置文件路径
Returns:
Dict[str, str]: 生成的文件路径和内容映射
Raises:
FileNotFoundError: 当YAML文件不存在时
ValueError: 当配置验证失败时
"""
# 解析YAML配置
config = self.yaml_parser.parse_yaml_file(yaml_file_path)
# 从配置生成代码
return self.generate_from_yaml_config(config)
def generate_from_yaml_config(self, config: Dict[str, Any]) -> Dict[str, str]:
"""
从YAML配置字典生成Django应用代码
Args:
config: YAML配置字典
Returns:
Dict[str, str]: 生成的文件路径和内容映射
"""
app_name = config['app_name']
models = config['models']
output_dir = config.get('output_dir', '.') # 默认输出到当前目录
all_generated_files = {}
# 为每个模型生成代码
if isinstance(models, dict):
# 如果models是字典格式
models_items = models.items()
else:
# 如果models是列表格式转换为字典格式
models_items = [(model['name'], model) for model in models]
for model_name, model_config in models_items:
# 获取模型字段配置
fields = model_config.get('fields', [])
operations = model_config.get('operations', ['create', 'get', 'update', 'delete', 'list'])
permissions = model_config.get('permissions', {})
api_config = model_config.get('api', {})
# 转换字段格式
processed_fields = {}
for field in fields:
field_name = field['name']
processed_fields[field_name] = {
'type': field['type'],
'verbose_name': field.get('verbose_name', field_name),
'help_text': field.get('help_text', f'{field_name}字段'),
}
# 添加其他字段属性
for attr in ['max_length', 'null', 'blank', 'default', 'unique', 'choices']:
if attr in field:
processed_fields[field_name][attr] = field[attr]
# 创建应用目录
app_dir = os.path.join(output_dir, app_name)
print(f"📁 创建应用目录: {app_dir}")
os.makedirs(app_dir, exist_ok=True)
# 创建migrations子目录
migrations_dir = os.path.join(app_dir, 'migrations')
print(f"📁 创建子目录: {migrations_dir}")
os.makedirs(migrations_dir, exist_ok=True)
# 创建migrations/__init__.py文件
migrations_init_file = os.path.join(migrations_dir, '__init__.py')
with open(migrations_init_file, 'w', encoding='utf-8') as f:
f.write('')
all_generated_files[migrations_init_file] = ''
# 生成模型代码
print(f"📝 生成模型代码...")
model_code = self.model_generator.generate(model_name, processed_fields)
model_file = os.path.join(app_dir, 'models.py')
with open(model_file, 'w', encoding='utf-8') as f:
f.write(model_code)
all_generated_files[model_file] = model_code
print(f"✅ 已生成: {model_file}")
# 生成序列化器代码
print(f"📝 生成序列化器代码...")
serializer_code = self.serializer_generator.generate(model_name, processed_fields)
serializer_file = os.path.join(app_dir, 'serializers.py')
with open(serializer_file, 'w', encoding='utf-8') as f:
f.write(serializer_code)
all_generated_files[serializer_file] = serializer_code
print(f"✅ 已生成: {serializer_file}")
# 生成视图代码
print(f"📝 生成视图代码...")
view_code = self.view_generator.generate(model_name, operations)
view_file = os.path.join(app_dir, 'views.py')
with open(view_file, 'w', encoding='utf-8') as f:
f.write(view_code)
all_generated_files[view_file] = view_code
print(f"✅ 已生成: {view_file}")
# 生成URL代码
print(f"📝 生成URL代码...")
url_code = self.url_generator.generate(model_name, operations)
url_file = os.path.join(app_dir, 'urls.py')
with open(url_file, 'w', encoding='utf-8') as f:
f.write(url_code)
all_generated_files[url_file] = url_code
print(f"✅ 已生成: {url_file}")
# 生成应用配置文件
print(f"📝 生成应用配置文件...")
apps_code = self._generate_apps_config(app_name)
apps_file = os.path.join(app_dir, 'apps.py')
with open(apps_file, 'w', encoding='utf-8') as f:
f.write(apps_code)
all_generated_files[apps_file] = apps_code
print(f"✅ 已生成: {apps_file}")
# 生成__init__.py文件
init_file = os.path.join(app_dir, '__init__.py')
with open(init_file, 'w', encoding='utf-8') as f:
f.write('')
all_generated_files[init_file] = ''
print(f"✅ 已生成: {init_file}")
# 生成migrations的__init__.py
migrations_init = os.path.join(app_dir, 'migrations', '__init__.py')
with open(migrations_init, 'w', encoding='utf-8') as f:
f.write('')
all_generated_files[migrations_init] = ''
print(f"✅ 已生成: {migrations_init}")
print(f"\n🎉 Django应用 '{app_name}' 生成完成!")
print(f"📁 输出目录: {os.path.abspath(output_dir)}")
print(f"📄 生成文件数量: {len(all_generated_files)}")
return all_generated_files
def generate_yaml_template(self, output_path: str = 'app_template.yaml') -> str:
"""
生成YAML配置文件模板
Args:
output_path: 输出文件路径
Returns:
str: 生成的模板内容
"""
return self.yaml_parser.generate_yaml_template(output_path)
def validate_yaml_config(self, yaml_file_path: str) -> bool:
"""
验证YAML配置文件格式
Args:
yaml_file_path: YAML配置文件路径
Returns:
bool: 验证是否通过
"""
try:
self.yaml_parser.parse_yaml_file(yaml_file_path)
return True
except Exception as e:
print(f"YAML配置验证失败: {e}")
return False

View File

@@ -0,0 +1,434 @@
#!/usr/bin/env python
"""
菜单权限生成器
用于自动生成菜单配置和权限同步功能
"""
from typing import Dict, List, Optional, Any
import os
from pathlib import Path
class MenuGenerator:
"""
菜单权限生成器
用于根据模型和操作自动生成菜单配置,包括:
1. 菜单项配置
2. 权限配置
3. 菜单层级结构
"""
def __init__(self):
"""初始化菜单生成器"""
self.menu_types = {
'directory': 1, # 目录
'menu': 2, # 菜单
'button': 3 # 按钮
}
# 标准操作映射 - 与视图生成器保持一致
self.operation_mapping = {
'list': {'name': '查询', 'permission': 'list'},
'create': {'name': '新增', 'permission': 'add'},
'retrieve': {'name': '详情', 'permission': 'list'}, # 统一使用list权限
'update': {'name': '修改', 'permission': 'edit'},
'delete': {'name': '删除', 'permission': 'remove'},
'export': {'name': '导出', 'permission': 'export'},
'import': {'name': '导入', 'permission': 'import'},
}
def generate_menu_config(
self,
module_name: str,
model_name: str,
operations: List[str],
parent_code: Optional[str] = None,
menu_prefix: str = 'system',
sort_order: int = 1,
icon: Optional[str] = None,
component_path: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
生成菜单配置
Args:
module_name: 模块名称(中文)
model_name: 模型名称英文snake_case
operations: 操作列表
parent_code: 父级菜单代码
menu_prefix: 菜单前缀
sort_order: 排序
icon: 图标
component_path: 组件路径
Returns:
List[Dict]: 菜单配置列表
"""
menus = []
# 生成主菜单
main_menu_code = f"{menu_prefix}:{model_name}"
main_menu = {
'menu_name': module_name,
'menu_code': main_menu_code,
'menu_type': self.menu_types['menu'],
'path': f"/{menu_prefix}/{model_name}",
'component': component_path or f"{menu_prefix}/{model_name}/index",
'icon': icon or model_name,
'permission': f"{main_menu_code}:list",
'sort_order': sort_order,
'parent_code': parent_code
}
menus.append(main_menu)
# 生成操作按钮
for i, operation in enumerate(operations, 1):
if operation in self.operation_mapping:
op_config = self.operation_mapping[operation]
button_menu = {
'menu_name': f"{module_name}{op_config['name']}",
'menu_code': f"{main_menu_code}:{op_config['permission']}",
'menu_type': self.menu_types['button'],
'permission': f"{main_menu_code}:{op_config['permission']}",
'sort_order': i,
'parent_code': main_menu_code
}
menus.append(button_menu)
return menus
def generate_directory_config(
self,
directory_name: str,
directory_code: str,
path: str,
icon: str = 'folder',
sort_order: int = 1
) -> Dict[str, Any]:
"""
生成目录配置
Args:
directory_name: 目录名称
directory_code: 目录代码
path: 路径
icon: 图标
sort_order: 排序
Returns:
Dict: 目录配置
"""
return {
'menu_name': directory_name,
'menu_code': directory_code,
'menu_type': self.menu_types['directory'],
'path': path,
'icon': icon,
'sort_order': sort_order,
'parent_code': None
}
def generate_custom_menu_config(
self,
menu_name: str,
menu_code: str,
operations: List[Dict[str, str]],
parent_code: Optional[str] = None,
path: Optional[str] = None,
component: Optional[str] = None,
icon: Optional[str] = None,
sort_order: int = 1
) -> List[Dict[str, Any]]:
"""
生成自定义菜单配置
Args:
menu_name: 菜单名称
menu_code: 菜单代码
operations: 自定义操作列表 [{'name': '操作名', 'permission': '权限码'}]
parent_code: 父级菜单代码
path: 路径
component: 组件
icon: 图标
sort_order: 排序
Returns:
List[Dict]: 菜单配置列表
"""
menus = []
# 生成主菜单
main_menu = {
'menu_name': menu_name,
'menu_code': menu_code,
'menu_type': self.menu_types['menu'],
'sort_order': sort_order,
'parent_code': parent_code
}
if path:
main_menu['path'] = path
if component:
main_menu['component'] = component
if icon:
main_menu['icon'] = icon
if operations:
main_menu['permission'] = f"{menu_code}:{operations[0]['permission']}"
menus.append(main_menu)
# 生成操作按钮
for i, operation in enumerate(operations, 1):
button_menu = {
'menu_name': f"{menu_name}{operation['name']}",
'menu_code': f"{menu_code}:{operation['permission']}",
'menu_type': self.menu_types['button'],
'permission': f"{menu_code}:{operation['permission']}",
'sort_order': i,
'parent_code': menu_code
}
menus.append(button_menu)
return menus
def update_menus_config_file(
self,
new_menus: List[Dict[str, Any]],
config_file_path: str = None
) -> bool:
"""
更新菜单配置文件
Args:
new_menus: 新的菜单配置列表
config_file_path: 配置文件路径
Returns:
bool: 更新是否成功
"""
if not config_file_path:
config_file_path = os.path.join(
Path(__file__).parent.parent,
'config',
'menus_config.py'
)
try:
# 读取现有配置
with open(config_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 查找现有菜单列表的结束位置
import_end = content.find(']')
if import_end == -1:
return False
# 生成新菜单配置代码
new_menu_code = self._generate_menu_code(new_menus)
# 在现有菜单列表末尾添加新菜单(在最后一个 ] 之前)
updated_content = (
content[:import_end] +
',\n\n # 新增菜单配置\n' +
new_menu_code +
content[import_end:]
)
# 写入更新后的配置
with open(config_file_path, 'w', encoding='utf-8') as f:
f.write(updated_content)
return True
except Exception as e:
print(f"更新菜单配置文件失败: {e}")
return False
def _generate_menu_code(self, menus: List[Dict[str, Any]]) -> str:
"""
生成菜单配置代码
Args:
menus: 菜单配置列表
Returns:
str: 菜单配置代码
"""
code_lines = []
for menu in menus:
code_lines.append(" {")
for key, value in menu.items():
if isinstance(value, str):
code_lines.append(f" '{key}': '{value}',")
elif value is None:
code_lines.append(f" '{key}': None,")
else:
code_lines.append(f" '{key}': {value},")
code_lines.append(" }")
return '\n'.join(code_lines)
def generate_permission_sync_code(
self,
menu_configs: List[Dict[str, Any]]
) -> str:
"""
生成权限同步代码
Args:
menu_configs: 菜单配置列表
Returns:
str: 权限同步代码
"""
sync_code = '''
def sync_new_menus():
"""
同步新增菜单到数据库
"""
from hertz_studio_django_auth.models import HertzMenu
from django.db import transaction
new_menus = [
'''
# 添加菜单配置
sync_code += self._generate_menu_code(menu_configs)
sync_code += '''
]
print("正在同步新增菜单...")
with transaction.atomic():
created_menus = {}
# 按层级创建菜单
for menu_data in new_menus:
parent_code = menu_data.pop('parent_code', None)
parent_id = None
if parent_code and parent_code in created_menus:
parent_id = created_menus[parent_code]
menu, created = HertzMenu.objects.get_or_create(
menu_code=menu_data['menu_code'],
defaults={
**menu_data,
'parent_id': parent_id
}
)
created_menus[menu.menu_code] = menu
if created:
print(f"菜单创建成功: {menu.menu_name}")
else:
print(f"菜单已存在: {menu.menu_name}")
print("菜单同步完成")
'''
return sync_code
def create_menu_sync_script(
self,
menu_configs: List[Dict[str, Any]],
script_path: str = None
) -> bool:
"""
创建菜单同步脚本
Args:
menu_configs: 菜单配置列表
script_path: 脚本路径
Returns:
bool: 创建是否成功
"""
if not script_path:
script_path = os.path.join(
Path(__file__).parent.parent.parent,
'sync_menus.py'
)
try:
script_content = f'''#!/usr/bin/env python
"""
菜单同步脚本
自动生成的菜单权限同步脚本
"""
import os
import sys
import django
# 添加项目路径
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
django.setup()
{self.generate_permission_sync_code(menu_configs)}
if __name__ == "__main__":
sync_new_menus()
'''
with open(script_path, 'w', encoding='utf-8') as f:
f.write(script_content)
return True
except Exception as e:
print(f"创建菜单同步脚本失败: {e}")
return False
# 使用示例
def example_usage():
"""使用示例"""
generator = MenuGenerator()
# 生成标准CRUD菜单
crud_menus = generator.generate_menu_config(
module_name="产品管理",
model_name="product",
operations=['list', 'create', 'update', 'delete'],
parent_code="system",
sort_order=5,
icon="product"
)
# 生成自定义菜单
custom_menus = generator.generate_custom_menu_config(
menu_name="报表管理",
menu_code="system:report",
operations=[
{'name': '查看', 'permission': 'view'},
{'name': '导出', 'permission': 'export'},
{'name': '打印', 'permission': 'print'}
],
parent_code="system",
path="/system/report",
component="system/report/index",
icon="report",
sort_order=8
)
# 更新配置文件
all_menus = crud_menus + custom_menus
generator.update_menus_config_file(all_menus)
# 创建同步脚本
generator.create_menu_sync_script(all_menus)
print("菜单配置生成完成")
if __name__ == "__main__":
example_usage()

View File

@@ -0,0 +1,262 @@
"""
Django模型代码生成器
该模块负责根据配置生成Django模型代码
"""
import os
from typing import Dict, List, Any, Optional
from .base_generator import BaseGenerator
class ModelGenerator(BaseGenerator):
"""Django模型代码生成器"""
def __init__(self):
"""初始化模型生成器"""
super().__init__()
def generate(self, model_name: str = None, fields: List[Dict[str, Any]] = None, **kwargs) -> str:
"""
生成Django模型代码
Args:
model_name: 模型名称
fields: 字段列表
**kwargs: 其他参数
Returns:
str: 生成的模型代码
"""
# 处理参数,支持位置参数和关键字参数
if model_name is None:
model_name = kwargs.get('model_name', 'DefaultModel')
if fields is None:
fields = kwargs.get('fields', [])
table_name = kwargs.get('table_name')
verbose_name = kwargs.get('verbose_name')
verbose_name_plural = kwargs.get('verbose_name_plural')
ordering = kwargs.get('ordering', ['-created_at'])
status_choices = kwargs.get('status_choices', [
('active', '激活'),
('inactive', '未激活'),
('deleted', '已删除')
])
# 确保verbose_name不为None
if verbose_name is None:
verbose_name = model_name
# 确保verbose_name_plural不为None
if verbose_name_plural is None:
verbose_name_plural = verbose_name + '列表'
# 处理字段列表,确保每个字段都有必要的属性
processed_fields = []
for field in fields:
if isinstance(field, dict):
# 字段已经在YAML解析器中处理过类型转换这里不再重复处理
processed_fields.append(field)
elif isinstance(field, str):
# 如果字段是字符串,转换为字典格式
processed_fields.append({
'name': field,
'type': 'CharField', # 直接使用Django字段类型
'django_field_type': 'CharField',
'max_length': 100,
'verbose_name': field,
'help_text': f'{field}字段'
})
# 添加默认的时间戳字段(如果不存在)
has_created_at = any(field.get('name') == 'created_at' for field in processed_fields)
has_updated_at = any(field.get('name') == 'updated_at' for field in processed_fields)
if not has_created_at:
processed_fields.append({
'name': 'created_at',
'type': 'DateTimeField',
'auto_now_add': True,
'verbose_name': '创建时间',
'help_text': '记录创建时间'
})
if not has_updated_at:
processed_fields.append({
'name': 'updated_at',
'type': 'DateTimeField',
'auto_now': True,
'verbose_name': '更新时间',
'help_text': '记录最后更新时间'
})
# 准备模板上下文
context = {
'model_name': model_name,
'fields': processed_fields,
'table_name': table_name or self.camel_to_snake(model_name),
'verbose_name': verbose_name,
'verbose_name_plural': verbose_name_plural,
'ordering': ordering,
'status_choices': status_choices,
'has_status_field': any(field.get('name') == 'status' for field in processed_fields)
}
# 渲染模板
return self.render_template('django/models.mako', context)
def generate_model_with_relationships(
self,
model_name: str,
fields: List[Dict[str, Any]],
relationships: List[Dict[str, Any]] = None,
**kwargs
) -> str:
"""
生成包含关系字段的模型代码
Args:
model_name: 模型名称
fields: 字段配置列表
relationships: 关系字段配置列表
Returns:
str: 生成的模型代码
"""
if relationships:
# 将关系字段添加到字段列表中
for rel in relationships:
fields.append(rel)
return self.generate(
model_name=model_name,
fields=fields,
**kwargs
)
def generate_abstract_model(
self,
model_name: str,
fields: List[Dict[str, Any]],
**kwargs
) -> str:
"""
生成抽象模型代码
Args:
model_name: 模型名称
fields: 字段配置列表
Returns:
str: 生成的抽象模型代码
"""
kwargs['abstract'] = True
return self.generate(
model_name=model_name,
fields=fields,
**kwargs
)
def generate_proxy_model(
self,
model_name: str,
base_model: str,
**kwargs
) -> str:
"""
生成代理模型代码
Args:
model_name: 模型名称
base_model: 基础模型名称
Returns:
str: 生成的代理模型代码
"""
kwargs['proxy'] = True
kwargs['base_model'] = base_model
return self.generate(
model_name=model_name,
fields=[],
**kwargs
)
def validate_field_config(self, field_config: Dict[str, Any]) -> bool:
"""
验证字段配置
Args:
field_config: 字段配置字典
Returns:
bool: 验证是否通过
"""
required_keys = ['name', 'type']
for key in required_keys:
if key not in field_config:
return False
# 验证字段类型
valid_types = {
'CharField', 'TextField', 'IntegerField', 'FloatField',
'DecimalField', 'BooleanField', 'DateField', 'DateTimeField',
'EmailField', 'URLField', 'ImageField', 'FileField',
'ForeignKey', 'ManyToManyField', 'OneToOneField', 'JSONField',
'SlugField', 'PositiveIntegerField', 'BigIntegerField',
'SmallIntegerField', 'UUIDField', 'TimeField'
}
if field_config['type'] not in valid_types:
return False
return True
def add_timestamp_fields(self, fields: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
添加时间戳字段
Args:
fields: 原字段列表
Returns:
List[Dict[str, Any]]: 包含时间戳字段的字段列表
"""
timestamp_fields = [
{
'name': 'created_at',
'type': 'DateTimeField',
'auto_now_add': True,
'verbose_name': '创建时间',
'help_text': '记录创建时间'
},
{
'name': 'updated_at',
'type': 'DateTimeField',
'auto_now': True,
'verbose_name': '更新时间',
'help_text': '记录最后更新时间'
}
]
return fields + timestamp_fields
def add_status_field(self, fields: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
添加状态字段
Args:
fields: 原字段列表
Returns:
List[Dict[str, Any]]: 包含状态字段的字段列表
"""
status_field = {
'name': 'status',
'type': 'IntegerField',
'choices': 'StatusChoices.choices',
'default': 'StatusChoices.ENABLED',
'verbose_name': '状态'
}
return fields + [status_field]

View File

@@ -0,0 +1,336 @@
"""
Django序列化器代码生成器
该模块负责根据配置生成Django REST Framework序列化器代码
"""
import os
from typing import Dict, List, Any, Optional
from .base_generator import BaseGenerator
class SerializerGenerator(BaseGenerator):
"""Django序列化器代码生成器"""
def __init__(self):
"""初始化序列化器生成器"""
super().__init__()
def generate(self, model_name: str = None, fields: List[Any] = None, **kwargs) -> str:
"""
生成Django序列化器代码
Args:
model_name: 模型名称
fields: 字段列表(可以是字符串列表或字典列表)
**kwargs: 其他参数
Returns:
str: 生成的序列化器代码
"""
# 处理参数,支持位置参数和关键字参数
if model_name is None:
model_name = kwargs.get('model_name', 'DefaultModel')
if fields is None:
fields = kwargs.get('fields', [])
# 处理字段列表,统一转换为字典格式
processed_fields = []
for field in fields:
if isinstance(field, str):
# 字符串字段转换为字典格式
field_config = {
'name': field,
'type': 'CharField',
'create': True,
'update': True,
'list': True,
'required': True,
'read_only': False
}
processed_fields.append(field_config)
elif isinstance(field, dict):
# 字典字段,确保有必要的属性
field_config = {
'name': field.get('name', 'unknown_field'),
'type': field.get('type', 'CharField'),
'create': field.get('create', True),
'update': field.get('update', True),
'list': field.get('list', True),
'required': field.get('required', True),
'read_only': field.get('read_only', False)
}
processed_fields.append(field_config)
# 添加默认字段(如果不存在)
field_names = [f['name'] for f in processed_fields]
if 'id' not in field_names:
processed_fields.insert(0, {
'name': 'id',
'type': 'IntegerField',
'create': False,
'update': False,
'list': True,
'required': False,
'read_only': True
})
if 'created_at' not in field_names:
processed_fields.append({
'name': 'created_at',
'type': 'DateTimeField',
'create': False,
'update': False,
'list': True,
'required': False,
'read_only': True
})
if 'updated_at' not in field_names:
processed_fields.append({
'name': 'updated_at',
'type': 'DateTimeField',
'create': False,
'update': False,
'list': True,
'required': False,
'read_only': True
})
# 生成字段列表
create_fields_list = [f['name'] for f in processed_fields if f.get('create', False)]
update_fields_list = [f['name'] for f in processed_fields if f.get('update', False)]
list_fields_list = [f['name'] for f in processed_fields if f.get('list', False)]
# 准备模板上下文
context = {
'model_name': model_name,
'model_name_lower': model_name.lower(),
'fields': processed_fields,
'create_fields_list': create_fields_list,
'update_fields_list': update_fields_list,
'list_fields_list': list_fields_list,
'has_create_serializer': bool(create_fields_list),
'has_update_serializer': bool(update_fields_list),
'has_list_serializer': bool(list_fields_list)
}
# 渲染模板
return self.render_template('django/serializers.mako', context)
def generate_crud_serializers(
self,
model_name: str,
fields: List[str],
create_fields: Optional[List[str]] = None,
update_fields: Optional[List[str]] = None,
list_fields: Optional[List[str]] = None,
validators: Optional[Dict[str, str]] = None
) -> str:
"""
生成CRUD序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
create_fields: 创建时使用的字段
update_fields: 更新时使用的字段
list_fields: 列表时显示的字段
validators: 字段验证器映射
Returns:
str: 生成的序列化器代码
"""
# 默认字段配置
if create_fields is None:
create_fields = [f for f in fields if f not in ['id', 'created_at', 'updated_at']]
if update_fields is None:
update_fields = [f for f in fields if f not in ['id', 'created_at', 'updated_at']]
if list_fields is None:
list_fields = fields
# 构建字段配置
field_configs = []
for field_name in fields:
field_config = {
'name': field_name,
'create': field_name in create_fields,
'update': field_name in update_fields,
'list': field_name in list_fields,
'validators': validators.get(field_name, {}) if validators else {}
}
field_configs.append(field_config)
return self.generate(
model_name=model_name,
fields=field_configs
)
def generate_nested_serializer(
self,
model_name: str,
fields: List[str],
nested_fields: Dict[str, Dict[str, Any]]
) -> str:
"""
生成嵌套序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
nested_fields: 嵌套字段配置
Returns:
str: 生成的嵌套序列化器代码
"""
# 构建字段配置,包含嵌套字段信息
field_configs = []
for field_name in fields:
field_config = {
'name': field_name,
'nested': field_name in nested_fields,
'nested_config': nested_fields.get(field_name, {})
}
field_configs.append(field_config)
return self.generate(
model_name=model_name,
fields=field_configs,
has_nested=True
)
def generate_read_only_serializer(
self,
model_name: str,
fields: List[str]
) -> str:
"""
生成只读序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
Returns:
str: 生成的只读序列化器代码
"""
field_configs = []
for field_name in fields:
field_config = {
'name': field_name,
'read_only': True
}
field_configs.append(field_config)
return self.generate(
model_name=model_name,
fields=field_configs,
read_only=True
)
def generate_create_serializer(
self,
model_name: str,
fields: List[str],
validators: Optional[Dict[str, str]] = None
) -> str:
"""
生成创建序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
validators: 字段验证器映射
Returns:
str: 生成的创建序列化器代码
"""
field_configs = []
for field_name in fields:
field_config = {
'name': field_name,
'validators': validators.get(field_name, {}) if validators else {}
}
field_configs.append(field_config)
return self.generate(
model_name=model_name,
fields=field_configs,
serializer_type='create'
)
def generate_update_serializer(
self,
model_name: str,
fields: List[str],
validators: Optional[Dict[str, str]] = None
) -> str:
"""
生成更新序列化器代码
Args:
model_name: 模型名称
fields: 字段列表
validators: 字段验证器映射
Returns:
str: 生成的更新序列化器代码
"""
field_configs = []
for field_name in fields:
field_config = {
'name': field_name,
'validators': validators.get(field_name, {}) if validators else {}
}
field_configs.append(field_config)
return self.generate(
model_name=model_name,
fields=field_configs,
serializer_type='update'
)
def add_custom_validation(
self,
serializer_code: str,
field_name: str,
validation_code: str
) -> str:
"""
添加自定义验证方法
Args:
serializer_code: 原序列化器代码
field_name: 字段名称
validation_code: 验证代码
Returns:
str: 包含自定义验证的序列化器代码
"""
validation_method = f"""
def validate_{field_name}(self, value):
\"\"\"
验证{field_name}字段
\"\"\"
{validation_code}
return value
"""
# 在类定义结束前插入验证方法
lines = serializer_code.split('\n')
insert_index = -1
for i, line in enumerate(lines):
if line.strip().startswith('class ') and 'Serializer' in line:
# 找到类定义结束的位置
for j in range(i + 1, len(lines)):
if lines[j].strip() and not lines[j].startswith(' '):
insert_index = j
break
break
if insert_index > 0:
lines.insert(insert_index, validation_method)
return '\n'.join(lines)

View File

@@ -0,0 +1,248 @@
"""
模板引擎类
基于Mako模板引擎的代码生成模板管理器
"""
import os
from typing import Dict, Any, Optional
from mako.template import Template
from mako.lookup import TemplateLookup
from mako.exceptions import TemplateLookupException, CompileException
class TemplateEngine:
"""
模板引擎类
负责管理和渲染Mako模板
"""
def __init__(self, template_dir: str = None):
"""
初始化模板引擎
Args:
template_dir: 模板目录路径
"""
if template_dir is None:
# 默认模板目录
current_dir = os.path.dirname(os.path.abspath(__file__))
template_dir = os.path.join(current_dir, 'templates')
self.template_dir = template_dir
self.lookup = TemplateLookup(
directories=[template_dir],
module_directory=os.path.join(template_dir, '.mako_modules'),
input_encoding='utf-8',
output_encoding='utf-8',
encoding_errors='replace'
)
def render_template(self, template_name: str, context: Dict[str, Any]) -> str:
"""
渲染模板
Args:
template_name: 模板文件名
context: 模板上下文变量
Returns:
str: 渲染后的代码字符串
Raises:
TemplateLookupException: 模板不存在
CompileException: 模板编译错误
"""
try:
template = self.lookup.get_template(template_name)
rendered = template.render(**context)
# 确保返回字符串而不是bytes
if isinstance(rendered, bytes):
return rendered.decode('utf-8')
return rendered
except TemplateLookupException as e:
raise TemplateLookupException(f"模板 '{template_name}' 不存在: {str(e)}")
except CompileException as e:
raise CompileException(f"模板 '{template_name}' 编译错误: {str(e)}")
except Exception as e:
raise Exception(f"渲染模板 '{template_name}' 时发生错误: {str(e)}")
def render_django_model(self, context: Dict[str, Any]) -> str:
"""
渲染Django模型模板
Args:
context: 模板上下文
Returns:
str: 生成的模型代码
"""
return self.render_template('django/models.mako', context)
def render_django_serializer(self, context: Dict[str, Any]) -> str:
"""
渲染Django序列化器模板
Args:
context: 模板上下文
Returns:
str: 生成的序列化器代码
"""
return self.render_template('django/serializers.mako', context)
def render_django_view(self, context: Dict[str, Any]) -> str:
"""
渲染Django视图模板
Args:
context: 模板上下文
Returns:
str: 生成的视图代码
"""
return self.render_template('django/views.mako', context)
def render_django_url(self, context: Dict[str, Any]) -> str:
"""
渲染Django URL配置模板
Args:
context: 模板上下文
Returns:
str: 生成的URL配置代码
"""
return self.render_template('django/urls.mako', context)
def render_django_app(self, context: Dict[str, Any]) -> str:
"""
渲染Django应用配置模板
Args:
context: 模板上下文
Returns:
str: 生成的应用配置代码
"""
return self.render_template('django/apps.mako', context)
def get_available_templates(self) -> list:
"""
获取可用的模板列表
Returns:
list: 模板文件列表
"""
templates = []
for root, dirs, files in os.walk(self.template_dir):
for file in files:
if file.endswith('.mako'):
rel_path = os.path.relpath(os.path.join(root, file), self.template_dir)
templates.append(rel_path.replace('\\', '/'))
return templates
def template_exists(self, template_name: str) -> bool:
"""
检查模板是否存在
Args:
template_name: 模板名称
Returns:
bool: 模板是否存在
"""
try:
self.lookup.get_template(template_name)
return True
except TemplateLookupException:
return False
def validate_template(self, template_name: str) -> tuple:
"""
验证模板语法
Args:
template_name: 模板名称
Returns:
tuple: (是否有效, 错误信息)
"""
try:
template = self.lookup.get_template(template_name)
# 尝试编译模板
template.code
return True, None
except CompileException as e:
return False, f"模板编译错误: {str(e)}"
except TemplateLookupException as e:
return False, f"模板不存在: {str(e)}"
except Exception as e:
return False, f"未知错误: {str(e)}"
def create_context(self, **kwargs) -> Dict[str, Any]:
"""
创建模板上下文
Args:
**kwargs: 上下文变量
Returns:
Dict[str, Any]: 模板上下文
"""
return kwargs
def add_template_directory(self, directory: str) -> None:
"""
添加模板目录
Args:
directory: 模板目录路径
"""
if os.path.exists(directory):
self.lookup.directories.append(directory)
else:
raise FileNotFoundError(f"模板目录不存在: {directory}")
def clear_cache(self) -> None:
"""
清除模板缓存
"""
self.lookup._collection.clear()
def get_template_info(self, template_name: str) -> Dict[str, Any]:
"""
获取模板信息
Args:
template_name: 模板名称
Returns:
Dict[str, Any]: 模板信息
"""
try:
template = self.lookup.get_template(template_name)
template_path = template.filename
info = {
'name': template_name,
'path': template_path,
'exists': True,
'size': os.path.getsize(template_path) if template_path else 0,
'modified_time': os.path.getmtime(template_path) if template_path else None
}
# 验证模板
is_valid, error = self.validate_template(template_name)
info['valid'] = is_valid
info['error'] = error
return info
except TemplateLookupException:
return {
'name': template_name,
'exists': False,
'valid': False,
'error': '模板不存在'
}

View File

@@ -0,0 +1,225 @@
# -*- coding:utf-8 -*-
from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
STOP_RENDERING = runtime.STOP_RENDERING
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1760426258.0375054
_enable_loop = True
_template_filename = 'C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/models.mako'
_template_uri = 'django/models.mako'
_source_encoding = 'utf-8'
_exports = []
from datetime import datetime
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
any = context.get('any', UNDEFINED)
chr = context.get('chr', UNDEFINED)
ordering = context.get('ordering', UNDEFINED)
fields = context.get('fields', UNDEFINED)
verbose_name = context.get('verbose_name', UNDEFINED)
model_name = context.get('model_name', UNDEFINED)
str = context.get('str', UNDEFINED)
table_name = context.get('table_name', UNDEFINED)
status_choices = context.get('status_choices', UNDEFINED)
tuple = context.get('tuple', UNDEFINED)
isinstance = context.get('isinstance', UNDEFINED)
len = context.get('len', UNDEFINED)
__M_writer = context.writer()
__M_writer('"""\r\nDjango模型模板\r\n"""\r\n')
__M_writer('\r\n')
# 导入必要的模块
imports = []
if any(field.get('type') == 'ForeignKey' for field in fields):
imports.append('from django.contrib.auth import get_user_model')
if any(field.get('type') in ['DateTimeField', 'DateField', 'TimeField'] for field in fields):
imports.append('from django.utils import timezone')
if any(field.get('choices') for field in fields):
imports.append('from django.core.validators import validate_email')
# 生成状态选择项
status_choices_code = ""
if status_choices:
choices_list = []
for choice in status_choices:
if isinstance(choice, tuple) and len(choice) == 2:
choices_list.append(f" ('{choice[0]}', '{choice[1]}')")
else:
choices_list.append(f" ('{choice}', '{choice}')")
status_choices_code = f""" STATUS_CHOICES = [
{',{}'.format(chr(10)).join(choices_list)},
]"""
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['field','choice','choices_list','imports','status_choices_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer('from django.db import models\r\n')
for import_line in imports:
__M_writer(str(import_line))
__M_writer('\r\n')
__M_writer('\r\nclass ')
__M_writer(str(model_name))
__M_writer('(models.Model):\r\n """\r\n ')
__M_writer(str(verbose_name or model_name))
__M_writer('模型\r\n')
if table_name:
__M_writer(' \r\n 数据表名: ')
__M_writer(str(table_name))
__M_writer('\r\n')
__M_writer(' 创建时间: ')
__M_writer(str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
__M_writer('\r\n """\r\n')
if status_choices_code:
__M_writer(str(status_choices_code))
__M_writer('\r\n\r\n')
for field in fields:
field_name = field['name']
# 使用转换后的Django字段类型
field_type = field.get('type', 'CharField')
field_options = []
# 处理字段选项
if field.get('verbose_name'):
field_options.append(f"verbose_name='{field['verbose_name']}'")
if field.get('help_text'):
field_options.append(f"help_text='{field['help_text']}'")
if field.get('max_length'):
field_options.append(f"max_length={field['max_length']}")
if field.get('null'):
field_options.append(f"null={field['null']}")
if field.get('blank'):
field_options.append(f"blank={field['blank']}")
if field.get('default') is not None:
if isinstance(field['default'], str):
field_options.append(f"default='{field['default']}'")
else:
field_options.append(f"default={field['default']}")
if field.get('unique'):
field_options.append(f"unique={field['unique']}")
if field.get('db_index'):
field_options.append(f"db_index={field['db_index']}")
# 处理特殊字段类型
if field_type == 'ForeignKey':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
else:
field_options.insert(0, "get_user_model()")
if field.get('on_delete'):
field_options.append(f"on_delete=models.{field['on_delete']}")
else:
field_options.append("on_delete=models.CASCADE")
elif field_type == 'ManyToManyField':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
elif field_type == 'OneToOneField':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
if field.get('on_delete'):
field_options.append(f"on_delete=models.{field['on_delete']}")
else:
field_options.append("on_delete=models.CASCADE")
# 处理选择项
if field.get('choices'):
if field_name == 'status' and status_choices:
field_options.append("choices=STATUS_CHOICES")
else:
choices_list = []
for choice in field['choices']:
if isinstance(choice, tuple) and len(choice) == 2:
choices_list.append(f"('{choice[0]}', '{choice[1]}')")
else:
choices_list.append(f"('{choice}', '{choice}')")
field_options.append(f"choices=[{', '.join(choices_list)}]")
# 处理特殊字段类型的额外参数
if field_type == 'DecimalField':
if not any('max_digits' in opt for opt in field_options):
field_options.append('max_digits=10')
if not any('decimal_places' in opt for opt in field_options):
field_options.append('decimal_places=2')
elif field_type == 'DateTimeField':
if field_name == 'created_at' and not any('auto_now_add' in opt for opt in field_options):
field_options.append('auto_now_add=True')
elif field_name == 'updated_at' and not any('auto_now' in opt for opt in field_options):
field_options.append('auto_now=True')
options_str = ', '.join(field_options) if field_options else ''
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['field_options','options_str','choice','choices_list','opt','field_name','field_type'] if __M_key in __M_locals_builtin_stored]))
__M_writer('\r\n ')
__M_writer(str(field_name))
__M_writer(' = models.')
__M_writer(str(field_type))
__M_writer('(')
__M_writer(str(options_str))
__M_writer(')\r\n')
__M_writer('\r\n class Meta:\r\n')
if table_name:
__M_writer(" db_table = '")
__M_writer(str(table_name))
__M_writer("'\r\n")
__M_writer(" verbose_name = '")
__M_writer(str(verbose_name or model_name))
__M_writer("'\r\n verbose_name_plural = '")
__M_writer(str(verbose_name or model_name))
__M_writer("'\r\n")
if ordering:
__M_writer(' ordering = [')
__M_writer(str(', '.join([f"'{field}'" for field in ordering])))
__M_writer(']\r\n')
else:
__M_writer(" ordering = ['-created_at']\r\n")
__M_writer('\r\n def __str__(self):\r\n """字符串表示"""\r\n')
if fields:
__M_writer(' ')
# 寻找合适的字段作为字符串表示
str_field = None
for field in fields:
if field['name'] in ['name', 'title', 'username', 'email']:
str_field = field['name']
break
if not str_field:
# 如果没有找到合适的字段,使用第一个字符串字段
for field in fields:
if field['type'] in ['CharField', 'TextField', 'EmailField']:
str_field = field['name']
break
if not str_field:
str_field = 'id'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['field','str_field'] if __M_key in __M_locals_builtin_stored]))
__M_writer('\r\n return str(self.')
__M_writer(str(str_field))
__M_writer(')\r\n')
else:
__M_writer(' return f"')
__M_writer(str(model_name))
__M_writer('({self.id})"\r\n')
__M_writer('\r\n def save(self, *args, **kwargs):\r\n """保存方法"""\r\n super().save(*args, **kwargs)\r\n\r\n @classmethod\r\n def get_by_id(cls, obj_id):\r\n """根据ID获取对象"""\r\n try:\r\n return cls.objects.get(id=obj_id)\r\n except cls.DoesNotExist:\r\n return None')
return ''
finally:
context.caller_stack._pop_frame()
"""
__M_BEGIN_METADATA
{"filename": "C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/models.mako", "uri": "django/models.mako", "source_encoding": "utf-8", "line_map": {"16": 4, "17": 5, "18": 6, "19": 7, "20": 0, "37": 1, "38": 6, "39": 7, "40": 8, "41": 9, "42": 10, "43": 11, "44": 12, "45": 13, "46": 14, "47": 15, "48": 16, "49": 17, "50": 18, "51": 19, "52": 20, "53": 21, "54": 22, "55": 23, "56": 24, "57": 25, "58": 26, "59": 27, "60": 28, "61": 29, "62": 30, "65": 30, "66": 31, "67": 32, "68": 32, "69": 34, "70": 35, "71": 35, "72": 37, "73": 37, "74": 38, "75": 39, "76": 40, "77": 40, "78": 42, "79": 42, "80": 42, "81": 44, "82": 45, "83": 45, "84": 48, "85": 49, "86": 50, "87": 51, "88": 52, "89": 53, "90": 54, "91": 55, "92": 56, "93": 57, "94": 58, "95": 59, "96": 60, "97": 61, "98": 62, "99": 63, "100": 64, "101": 65, "102": 66, "103": 67, "104": 68, "105": 69, "106": 70, "107": 71, "108": 72, "109": 73, "110": 74, "111": 75, "112": 76, "113": 77, "114": 78, "115": 79, "116": 80, "117": 81, "118": 82, "119": 83, "120": 84, "121": 85, "122": 86, "123": 87, "124": 88, "125": 89, "126": 90, "127": 91, "128": 92, "129": 93, "130": 94, "131": 95, "132": 96, "133": 97, "134": 98, "135": 99, "136": 100, "137": 101, "138": 102, "139": 103, "140": 104, "141": 105, "142": 106, "143": 107, "144": 108, "145": 109, "146": 110, "147": 111, "148": 112, "149": 113, "150": 114, "151": 115, "152": 116, "153": 117, "154": 118, "155": 119, "156": 120, "157": 121, "158": 122, "159": 123, "160": 124, "163": 123, "164": 124, "165": 124, "166": 124, "167": 124, "168": 124, "169": 124, "170": 126, "171": 128, "172": 129, "173": 129, "174": 129, "175": 131, "176": 131, "177": 131, "178": 132, "179": 132, "180": 133, "181": 134, "182": 134, "183": 134, "184": 135, "185": 136, "186": 138, "187": 141, "188": 142, "189": 142, "190": 143, "191": 144, "192": 145, "193": 146, "194": 147, "195": 148, "196": 149, "197": 150, "198": 151, "199": 152, "200": 153, "201": 154, "202": 155, "203": 156, "204": 157, "205": 158, "208": 157, "209": 158, "210": 158, "211": 159, "212": 160, "213": 160, "214": 160, "215": 162, "221": 215}}
__M_END_METADATA
"""

View File

@@ -0,0 +1,173 @@
# -*- coding:utf-8 -*-
from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
STOP_RENDERING = runtime.STOP_RENDERING
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1760424185.601884
_enable_loop = True
_template_filename = 'C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/serializers.mako'
_template_uri = 'django/serializers.mako'
_source_encoding = 'utf-8'
_exports = []
from datetime import datetime
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
model_name = context.get('model_name', UNDEFINED)
fields = context.get('fields', UNDEFINED)
__M_writer = context.writer()
__M_writer('"""\nDjango序列化器模板\n"""\n')
__M_writer('\n')
# 生成字段列表
all_fields = []
create_fields_list = []
update_fields_list = []
list_fields_list = []
# 处理字段列表
for field in fields:
field_name = field['name']
all_fields.append(field_name)
# 排除自动生成的字段
if field_name not in ['id', 'created_at', 'updated_at']:
create_fields_list.append(field_name)
update_fields_list.append(field_name)
list_fields_list.append(field_name)
# 添加默认字段
if 'id' not in all_fields:
all_fields.insert(0, 'id')
if 'created_at' not in all_fields:
all_fields.append('created_at')
if 'updated_at' not in all_fields:
all_fields.append('updated_at')
# 如果没有指定列表字段,使用所有字段
if not list_fields_list:
list_fields_list = all_fields
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['all_fields','field_name','update_fields_list','field','create_fields_list','list_fields_list'] if __M_key in __M_locals_builtin_stored]))
__M_writer('\nfrom rest_framework import serializers\nfrom .models import ')
__M_writer(str(model_name))
__M_writer('\n\n\nclass ')
__M_writer(str(model_name))
__M_writer('Serializer(serializers.ModelSerializer):\n """\n ')
__M_writer(str(model_name))
__M_writer('序列化器\n \n 用于')
__M_writer(str(model_name))
__M_writer('模型的序列化和反序列化\n 创建时间: ')
__M_writer(str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
__M_writer('\n """\n \n class Meta:\n model = ')
__M_writer(str(model_name))
__M_writer('\n fields = [')
__M_writer(str(', '.join([f"'{field}'" for field in all_fields])))
__M_writer("]\n read_only_fields = ['id', 'created_at', 'updated_at']\n \n")
for field in fields:
pass
if field.get('validators'):
__M_writer(' def validate_')
__M_writer(str(field['name']))
__M_writer('(self, value):\n """\n 验证')
__M_writer(str(field['name']))
__M_writer('字段\n """\n')
for validator_name, validator_rule in field['validators'].items():
__M_writer(' # ')
__M_writer(str(validator_name))
__M_writer('验证: ')
__M_writer(str(validator_rule))
__M_writer('\n')
__M_writer(' return value\n \n')
__M_writer(' def validate(self, attrs):\n """\n 对象级别的验证\n """\n # 在这里添加跨字段验证逻辑\n return attrs\n \n def create(self, validated_data):\n """\n 创建')
__M_writer(str(model_name))
__M_writer('实例\n """\n return ')
__M_writer(str(model_name))
__M_writer('.objects.create(**validated_data)\n \n def update(self, instance, validated_data):\n """\n 更新')
__M_writer(str(model_name))
__M_writer('实例\n """\n for attr, value in validated_data.items():\n setattr(instance, attr, value)\n instance.save()\n return instance\n\n\nclass ')
__M_writer(str(model_name))
__M_writer('CreateSerializer(serializers.ModelSerializer):\n """\n ')
__M_writer(str(model_name))
__M_writer('创建序列化器\n \n 用于创建')
__M_writer(str(model_name))
__M_writer('实例\n """\n \n class Meta:\n model = ')
__M_writer(str(model_name))
__M_writer('\n fields = [')
__M_writer(str(', '.join([f"'{field}'" for field in create_fields_list])))
__M_writer(']\n \n')
for field in fields:
pass
if field['name'] in create_fields_list and field.get('validators'):
__M_writer(' def validate_')
__M_writer(str(field['name']))
__M_writer('(self, value):\n """\n 验证')
__M_writer(str(field['name']))
__M_writer('字段\n """\n')
for validator_name, validator_rule in field['validators'].items():
__M_writer(' # ')
__M_writer(str(validator_name))
__M_writer('验证: ')
__M_writer(str(validator_rule))
__M_writer('\n')
__M_writer(' return value\n \n')
__M_writer('\n\nclass ')
__M_writer(str(model_name))
__M_writer('UpdateSerializer(serializers.ModelSerializer):\n """\n ')
__M_writer(str(model_name))
__M_writer('更新序列化器\n \n 用于更新')
__M_writer(str(model_name))
__M_writer('实例\n """\n \n class Meta:\n model = ')
__M_writer(str(model_name))
__M_writer('\n fields = [')
__M_writer(str(', '.join([f"'{field}'" for field in update_fields_list])))
__M_writer(']\n \n')
for field in fields:
pass
if field['name'] in update_fields_list and field.get('validators'):
__M_writer(' def validate_')
__M_writer(str(field['name']))
__M_writer('(self, value):\n """\n 验证')
__M_writer(str(field['name']))
__M_writer('字段\n """\n')
for validator_name, validator_rule in field['validators'].items():
__M_writer(' # ')
__M_writer(str(validator_name))
__M_writer('验证: ')
__M_writer(str(validator_rule))
__M_writer('\n')
__M_writer(' return value\n \n')
__M_writer('\n\nclass ')
__M_writer(str(model_name))
__M_writer('ListSerializer(serializers.ModelSerializer):\n """\n ')
__M_writer(str(model_name))
__M_writer('列表序列化器\n \n 用于列表显示')
__M_writer(str(model_name))
__M_writer('实例\n """\n \n class Meta:\n model = ')
__M_writer(str(model_name))
__M_writer('\n fields = [')
__M_writer(str(', '.join([f"'{field}'" for field in list_fields_list])))
__M_writer(']\n read_only_fields = [')
__M_writer(str(', '.join([f"'{field}'" for field in list_fields_list])))
__M_writer(']')
return ''
finally:
context.caller_stack._pop_frame()
"""
__M_BEGIN_METADATA
{"filename": "C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/serializers.mako", "uri": "django/serializers.mako", "source_encoding": "utf-8", "line_map": {"16": 4, "17": 5, "18": 6, "19": 7, "20": 0, "27": 1, "28": 6, "29": 7, "30": 8, "31": 9, "32": 10, "33": 11, "34": 12, "35": 13, "36": 14, "37": 15, "38": 16, "39": 17, "40": 18, "41": 19, "42": 20, "43": 21, "44": 22, "45": 23, "46": 24, "47": 25, "48": 26, "49": 27, "50": 28, "51": 29, "52": 30, "53": 31, "54": 32, "55": 33, "56": 34, "57": 35, "58": 36, "59": 37, "60": 38, "63": 37, "64": 39, "65": 39, "66": 42, "67": 42, "68": 44, "69": 44, "70": 46, "71": 46, "72": 47, "73": 47, "74": 51, "75": 51, "76": 52, "77": 52, "78": 55, "80": 56, "81": 57, "82": 57, "83": 57, "84": 59, "85": 59, "86": 61, "87": 62, "88": 62, "89": 62, "90": 62, "91": 62, "92": 64, "93": 68, "94": 77, "95": 77, "96": 79, "97": 79, "98": 83, "99": 83, "100": 91, "101": 91, "102": 93, "103": 93, "104": 95, "105": 95, "106": 99, "107": 99, "108": 100, "109": 100, "110": 102, "112": 103, "113": 104, "114": 104, "115": 104, "116": 106, "117": 106, "118": 108, "119": 109, "120": 109, "121": 109, "122": 109, "123": 109, "124": 111, "125": 115, "126": 117, "127": 117, "128": 119, "129": 119, "130": 121, "131": 121, "132": 125, "133": 125, "134": 126, "135": 126, "136": 128, "138": 129, "139": 130, "140": 130, "141": 130, "142": 132, "143": 132, "144": 134, "145": 135, "146": 135, "147": 135, "148": 135, "149": 135, "150": 137, "151": 141, "152": 143, "153": 143, "154": 145, "155": 145, "156": 147, "157": 147, "158": 151, "159": 151, "160": 152, "161": 152, "162": 153, "163": 153, "169": 163}}
__M_END_METADATA
"""

View File

@@ -0,0 +1,129 @@
# -*- coding:utf-8 -*-
from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
STOP_RENDERING = runtime.STOP_RENDERING
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1760498624.445131
_enable_loop = True
_template_filename = 'C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/urls.mako'
_template_uri = 'django/urls.mako'
_source_encoding = 'utf-8'
_exports = []
from datetime import datetime
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
app_name = context.get('app_name', UNDEFINED)
operations = context.get('operations', UNDEFINED)
model_name = context.get('model_name', UNDEFINED)
prefix = context.get('prefix', UNDEFINED)
__M_writer = context.writer()
__M_writer('"""\nDjango URL配置模板\n"""\n')
__M_writer('\n')
# 生成操作列表
operations_list = operations or ['create', 'get', 'update', 'delete', 'list']
snake_model_name = model_name.lower()
resource_name = snake_model_name
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['operations_list','resource_name','snake_model_name'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\nfrom django.urls import path\nfrom . import views\n\napp_name = '")
__M_writer(str(app_name or snake_model_name))
__M_writer("'\n\nurlpatterns = [\n")
if 'create' in operations_list:
__M_writer(' # 创建')
__M_writer(str(model_name))
__M_writer("\n path('', views.create_")
__M_writer(str(snake_model_name))
__M_writer(", name='create_")
__M_writer(str(snake_model_name))
__M_writer("'),\n")
__M_writer(' \n')
if 'list' in operations_list:
__M_writer(' # 获取')
__M_writer(str(model_name))
__M_writer("列表\n path('list/', views.list_")
__M_writer(str(snake_model_name))
__M_writer(", name='list_")
__M_writer(str(snake_model_name))
__M_writer("'),\n")
__M_writer(' \n')
if 'get' in operations_list:
__M_writer(' # 获取')
__M_writer(str(model_name))
__M_writer("详情\n path('<int:")
__M_writer(str(snake_model_name))
__M_writer("_id>/', views.get_")
__M_writer(str(snake_model_name))
__M_writer(", name='get_")
__M_writer(str(snake_model_name))
__M_writer("'),\n")
__M_writer(' \n')
if 'update' in operations_list:
__M_writer(' # 更新')
__M_writer(str(model_name))
__M_writer("\n path('<int:")
__M_writer(str(snake_model_name))
__M_writer("_id>/update/', views.update_")
__M_writer(str(snake_model_name))
__M_writer(", name='update_")
__M_writer(str(snake_model_name))
__M_writer("'),\n")
__M_writer(' \n')
if 'delete' in operations_list:
__M_writer(' # 删除')
__M_writer(str(model_name))
__M_writer("\n path('<int:")
__M_writer(str(snake_model_name))
__M_writer("_id>/delete/', views.delete_")
__M_writer(str(snake_model_name))
__M_writer(", name='delete_")
__M_writer(str(snake_model_name))
__M_writer("'),\n")
__M_writer(']\n\n# RESTful风格的URL配置可选\nrestful_urlpatterns = [\n')
if 'create' in operations_list or 'list' in operations_list:
__M_writer(' # POST: 创建')
__M_writer(str(model_name))
__M_writer(', GET: 获取')
__M_writer(str(model_name))
__M_writer("列表\n path('")
__M_writer(str(prefix))
__M_writer(str(resource_name))
__M_writer("/', views.")
__M_writer(str('create_' + snake_model_name if 'create' in operations_list else 'list_' + snake_model_name))
__M_writer(", name='")
__M_writer(str(snake_model_name))
__M_writer("_collection'),\n")
__M_writer(' \n')
if 'get' in operations_list or 'update' in operations_list or 'delete' in operations_list:
__M_writer(" # GET: 获取详情, PUT/PATCH: 更新, DELETE: 删除\n path('")
__M_writer(str(prefix))
__M_writer(str(resource_name))
__M_writer('/<int:')
__M_writer(str(snake_model_name))
__M_writer("_id>/', views.update_")
__M_writer(str(snake_model_name))
__M_writer(", name='")
__M_writer(str(snake_model_name))
__M_writer("_detail'),\n")
__M_writer(']')
return ''
finally:
context.caller_stack._pop_frame()
"""
__M_BEGIN_METADATA
{"filename": "C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/urls.mako", "uri": "django/urls.mako", "source_encoding": "utf-8", "line_map": {"16": 4, "17": 5, "18": 6, "19": 7, "20": 0, "29": 1, "30": 6, "31": 7, "32": 8, "33": 9, "34": 10, "35": 11, "36": 12, "37": 13, "40": 12, "41": 16, "42": 16, "43": 19, "44": 20, "45": 20, "46": 20, "47": 21, "48": 21, "49": 21, "50": 21, "51": 23, "52": 24, "53": 25, "54": 25, "55": 25, "56": 26, "57": 26, "58": 26, "59": 26, "60": 28, "61": 29, "62": 30, "63": 30, "64": 30, "65": 31, "66": 31, "67": 31, "68": 31, "69": 31, "70": 31, "71": 33, "72": 34, "73": 35, "74": 35, "75": 35, "76": 36, "77": 36, "78": 36, "79": 36, "80": 36, "81": 36, "82": 38, "83": 39, "84": 40, "85": 40, "86": 40, "87": 41, "88": 41, "89": 41, "90": 41, "91": 41, "92": 41, "93": 43, "94": 47, "95": 48, "96": 48, "97": 48, "98": 48, "99": 48, "100": 49, "101": 49, "102": 49, "103": 49, "104": 49, "105": 49, "106": 49, "107": 51, "108": 52, "109": 53, "110": 54, "111": 54, "112": 54, "113": 54, "114": 54, "115": 54, "116": 54, "117": 54, "118": 54, "119": 56, "125": 119}}
__M_END_METADATA
"""

View File

@@ -0,0 +1,328 @@
# -*- coding:utf-8 -*-
from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
STOP_RENDERING = runtime.STOP_RENDERING
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1760498624.4341366
_enable_loop = True
_template_filename = 'C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/views.mako'
_template_uri = 'django/views.mako'
_source_encoding = 'utf-8'
_exports = []
from datetime import datetime
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
operations = context.get('operations', UNDEFINED)
pagination = context.get('pagination', UNDEFINED)
isinstance = context.get('isinstance', UNDEFINED)
permissions = context.get('permissions', UNDEFINED)
dict = context.get('dict', UNDEFINED)
ordering = context.get('ordering', UNDEFINED)
model_name = context.get('model_name', UNDEFINED)
search_fields = context.get('search_fields', UNDEFINED)
__M_writer = context.writer()
__M_writer('"""\nDjango视图模板\n"""\n')
__M_writer('\n')
# 生成操作列表
operations_list = operations or ['create', 'get', 'update', 'delete', 'list']
permissions_list = permissions or []
snake_model_name = model_name.lower()
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['operations_list','snake_model_name','permissions_list'] if __M_key in __M_locals_builtin_stored]))
__M_writer('\nfrom rest_framework.decorators import api_view\nfrom rest_framework.response import Response\nfrom rest_framework import status\nfrom drf_spectacular.utils import extend_schema, OpenApiResponse\nfrom django.core.paginator import Paginator\nfrom django.db.models import Q\n\nfrom .models import ')
__M_writer(str(model_name))
__M_writer('\nfrom .serializers import (\n ')
__M_writer(str(model_name))
__M_writer('Serializer,\n ')
__M_writer(str(model_name))
__M_writer('CreateSerializer,\n ')
__M_writer(str(model_name))
__M_writer('UpdateSerializer,\n ')
__M_writer(str(model_name))
__M_writer('ListSerializer\n)\nfrom hertz_studio_django_utils.responses import HertzResponse\n')
if permissions_list:
__M_writer('from hertz_studio_django_auth.utils.decorators import login_required, permission_required\n')
__M_writer('\n\n')
if 'create' in operations_list:
__M_writer("@extend_schema(\n operation_id='create_")
__M_writer(str(snake_model_name))
__M_writer("',\n summary='创建")
__M_writer(str(model_name))
__M_writer("',\n description='创建新的")
__M_writer(str(model_name))
__M_writer("实例',\n request=")
__M_writer(str(model_name))
__M_writer('CreateSerializer,\n responses={\n 201: OpenApiResponse(response=')
__M_writer(str(model_name))
__M_writer("Serializer, description='创建成功'),\n 400: OpenApiResponse(description='参数错误'),\n },\n tags=['")
__M_writer(str(model_name))
__M_writer("']\n)\n@api_view(['POST'])\n")
if 'create' in permissions_list:
# 获取权限代码如果permissions_list是字典则获取对应的权限代码
if isinstance(permissions_list, dict) and 'create' in permissions_list:
permission_code = permissions_list['create']
else:
permission_code = f'{snake_model_name}:create'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['permission_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\n@permission_required('")
__M_writer(str(permission_code))
__M_writer("')\n")
__M_writer('def create_')
__M_writer(str(snake_model_name))
__M_writer('(request):\n """\n 创建')
__M_writer(str(model_name))
__M_writer('\n \n 创建时间: ')
__M_writer(str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
__M_writer('\n """\n try:\n serializer = ')
__M_writer(str(model_name))
__M_writer('CreateSerializer(data=request.data)\n if serializer.is_valid():\n instance = serializer.save()\n response_serializer = ')
__M_writer(str(model_name))
__M_writer("Serializer(instance)\n return HertzResponse.success(\n data=response_serializer.data,\n message='")
__M_writer(str(model_name))
__M_writer("创建成功'\n )\n return HertzResponse.validation_error(\n message='参数验证失败',\n errors=serializer.errors\n )\n except Exception as e:\n return HertzResponse.error(\n message='创建")
__M_writer(str(model_name))
__M_writer("失败',\n error=str(e)\n )\n\n\n")
if 'get' in operations_list or 'retrieve' in operations_list:
__M_writer("@extend_schema(\n operation_id='get_")
__M_writer(str(snake_model_name))
__M_writer("',\n summary='获取")
__M_writer(str(model_name))
__M_writer("详情',\n description='根据ID获取")
__M_writer(str(model_name))
__M_writer("详情',\n responses={\n 200: OpenApiResponse(response=")
__M_writer(str(model_name))
__M_writer("Serializer, description='获取成功'),\n 404: OpenApiResponse(description='")
__M_writer(str(model_name))
__M_writer("不存在'),\n },\n tags=['")
__M_writer(str(model_name))
__M_writer("']\n)\n@api_view(['GET'])\n")
if 'get' in permissions_list or 'retrieve' in permissions_list:
# 获取权限代码
if isinstance(permissions_list, dict):
permission_code = permissions_list.get('get') or permissions_list.get('retrieve') or f'{snake_model_name}:query'
else:
permission_code = f'{snake_model_name}:query'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['permission_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\n@permission_required('")
__M_writer(str(permission_code))
__M_writer("')\n")
__M_writer('def get_')
__M_writer(str(snake_model_name))
__M_writer('(request, ')
__M_writer(str(snake_model_name))
__M_writer('_id):\n """\n 获取')
__M_writer(str(model_name))
__M_writer('详情\n \n Args:\n ')
__M_writer(str(snake_model_name))
__M_writer('_id: ')
__M_writer(str(model_name))
__M_writer('ID\n """\n try:\n instance = ')
__M_writer(str(model_name))
__M_writer('.get_by_id(')
__M_writer(str(snake_model_name))
__M_writer("_id)\n if not instance:\n return HertzResponse.not_found(message='")
__M_writer(str(model_name))
__M_writer("不存在')\n \n serializer = ")
__M_writer(str(model_name))
__M_writer("Serializer(instance)\n return HertzResponse.success(\n data=serializer.data,\n message='获取")
__M_writer(str(model_name))
__M_writer("详情成功'\n )\n except Exception as e:\n return HertzResponse.error(\n message='获取")
__M_writer(str(model_name))
__M_writer("详情失败',\n error=str(e)\n )\n\n\n")
if 'update' in operations_list:
__M_writer("@extend_schema(\n operation_id='update_")
__M_writer(str(snake_model_name))
__M_writer("',\n summary='更新")
__M_writer(str(model_name))
__M_writer("',\n description='根据ID更新")
__M_writer(str(model_name))
__M_writer("信息',\n request=")
__M_writer(str(model_name))
__M_writer('UpdateSerializer,\n responses={\n 200: OpenApiResponse(response=')
__M_writer(str(model_name))
__M_writer("Serializer, description='更新成功'),\n 404: OpenApiResponse(description='")
__M_writer(str(model_name))
__M_writer("不存在'),\n 400: OpenApiResponse(description='参数错误'),\n },\n tags=['")
__M_writer(str(model_name))
__M_writer("']\n)\n@api_view(['PUT', 'PATCH'])\n")
if 'update' in permissions_list:
# 获取权限代码
if isinstance(permissions_list, dict) and 'update' in permissions_list:
permission_code = permissions_list['update']
else:
permission_code = f'{snake_model_name}:update'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['permission_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\n@permission_required('")
__M_writer(str(permission_code))
__M_writer("')\n")
__M_writer('def update_')
__M_writer(str(snake_model_name))
__M_writer('(request, ')
__M_writer(str(snake_model_name))
__M_writer('_id):\n """\n 更新')
__M_writer(str(model_name))
__M_writer('\n \n Args:\n ')
__M_writer(str(snake_model_name))
__M_writer('_id: ')
__M_writer(str(model_name))
__M_writer('ID\n """\n try:\n instance = ')
__M_writer(str(model_name))
__M_writer('.get_by_id(')
__M_writer(str(snake_model_name))
__M_writer("_id)\n if not instance:\n return HertzResponse.not_found(message='")
__M_writer(str(model_name))
__M_writer("不存在')\n \n partial = request.method == 'PATCH'\n serializer = ")
__M_writer(str(model_name))
__M_writer('UpdateSerializer(\n instance, \n data=request.data, \n partial=partial\n )\n \n if serializer.is_valid():\n updated_instance = serializer.save()\n response_serializer = ')
__M_writer(str(model_name))
__M_writer("Serializer(updated_instance)\n return HertzResponse.success(\n data=response_serializer.data,\n message='")
__M_writer(str(model_name))
__M_writer("更新成功'\n )\n return HertzResponse.validation_error(\n message='参数验证失败',\n errors=serializer.errors\n )\n except Exception as e:\n return HertzResponse.error(\n message='更新")
__M_writer(str(model_name))
__M_writer("失败',\n error=str(e)\n )\n\n\n")
if 'delete' in operations_list:
__M_writer("@extend_schema(\n operation_id='delete_")
__M_writer(str(snake_model_name))
__M_writer("',\n summary='删除")
__M_writer(str(model_name))
__M_writer("',\n description='根据ID删除")
__M_writer(str(model_name))
__M_writer("',\n responses={\n 200: OpenApiResponse(description='删除成功'),\n 404: OpenApiResponse(description='")
__M_writer(str(model_name))
__M_writer("不存在'),\n },\n tags=['")
__M_writer(str(model_name))
__M_writer("']\n)\n@api_view(['DELETE'])\n")
if 'delete' in permissions_list:
# 获取权限代码
if isinstance(permissions_list, dict) and 'delete' in permissions_list:
permission_code = permissions_list['delete']
else:
permission_code = f'{snake_model_name}:delete'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['permission_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\n@permission_required('")
__M_writer(str(permission_code))
__M_writer("')\n")
__M_writer('def delete_')
__M_writer(str(snake_model_name))
__M_writer('(request, ')
__M_writer(str(snake_model_name))
__M_writer('_id):\n """\n 删除')
__M_writer(str(model_name))
__M_writer('\n \n Args:\n ')
__M_writer(str(snake_model_name))
__M_writer('_id: ')
__M_writer(str(model_name))
__M_writer('ID\n """\n try:\n instance = ')
__M_writer(str(model_name))
__M_writer('.get_by_id(')
__M_writer(str(snake_model_name))
__M_writer("_id)\n if not instance:\n return HertzResponse.not_found(message='")
__M_writer(str(model_name))
__M_writer("不存在')\n \n instance.delete()\n return HertzResponse.success(message='")
__M_writer(str(model_name))
__M_writer("删除成功')\n except Exception as e:\n return HertzResponse.error(\n message='删除")
__M_writer(str(model_name))
__M_writer("失败',\n error=str(e)\n )\n\n\n")
if 'list' in operations_list:
__M_writer("@extend_schema(\n operation_id='list_")
__M_writer(str(snake_model_name))
__M_writer("',\n summary='获取")
__M_writer(str(model_name))
__M_writer("列表',\n description='分页获取")
__M_writer(str(model_name))
__M_writer("列表',\n responses={\n 200: OpenApiResponse(response=")
__M_writer(str(model_name))
__M_writer("ListSerializer, description='获取成功'),\n },\n tags=['")
__M_writer(str(model_name))
__M_writer("']\n)\n@api_view(['GET'])\n")
if 'list' in permissions_list:
# 获取权限代码
if isinstance(permissions_list, dict) and 'list' in permissions_list:
permission_code = permissions_list['list']
else:
permission_code = f'{snake_model_name}:list'
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['permission_code'] if __M_key in __M_locals_builtin_stored]))
__M_writer("\n@permission_required('")
__M_writer(str(permission_code))
__M_writer("')\n")
__M_writer('def list_')
__M_writer(str(snake_model_name))
__M_writer('(request):\n """\n 获取')
__M_writer(str(model_name))
__M_writer('列表\n \n 支持分页、搜索和排序\n """\n try:\n queryset = ')
__M_writer(str(model_name))
__M_writer(".objects.all()\n \n # 搜索功能\n search = request.GET.get('search', '')\n if search:\n")
if search_fields:
__M_writer(' search_q = Q()\n')
for field in search_fields:
__M_writer(' search_q |= Q(')
__M_writer(str(field))
__M_writer('__icontains=search)\n')
__M_writer(' queryset = queryset.filter(search_q)\n')
else:
__M_writer(' # 默认搜索字段,可根据需要调整\n queryset = queryset.filter(\n Q(id__icontains=search)\n )\n')
__M_writer(" \n # 排序功能\n ordering = request.GET.get('ordering', '-created_at')\n")
if ordering:
__M_writer(' valid_orderings = [')
__M_writer(str(', '.join([f"'{field}'" for field in ordering] + [f"'-{field}'" for field in ordering])))
__M_writer(']\n')
else:
__M_writer(" valid_orderings = ['created_at', '-created_at', 'updated_at', '-updated_at']\n")
__M_writer(' if ordering in valid_orderings:\n queryset = queryset.order_by(ordering)\n \n # 分页功能\n')
if pagination:
__M_writer(" page = int(request.GET.get('page', 1))\n page_size = int(request.GET.get('page_size', 20))\n paginator = Paginator(queryset, page_size)\n page_obj = paginator.get_page(page)\n \n serializer = ")
__M_writer(str(model_name))
__M_writer("ListSerializer(page_obj.object_list, many=True)\n \n return HertzResponse.success(\n data={\n 'results': serializer.data,\n 'pagination': {\n 'page': page,\n 'page_size': page_size,\n 'total_pages': paginator.num_pages,\n 'total_count': paginator.count,\n 'has_next': page_obj.has_next(),\n 'has_previous': page_obj.has_previous(),\n }\n },\n message='获取")
__M_writer(str(model_name))
__M_writer("列表成功'\n )\n")
else:
__M_writer(' serializer = ')
__M_writer(str(model_name))
__M_writer("ListSerializer(queryset, many=True)\n return HertzResponse.success(\n data=serializer.data,\n message='获取")
__M_writer(str(model_name))
__M_writer("列表成功'\n )\n")
__M_writer(" except Exception as e:\n return HertzResponse.error(\n message='获取")
__M_writer(str(model_name))
__M_writer("列表失败',\n error=str(e)\n )\n\n\n")
return ''
finally:
context.caller_stack._pop_frame()
"""
__M_BEGIN_METADATA
{"filename": "C:/2025.8/project/9/moban/hertz_server_django/hertz_studio_django_utils/code_generator/templates/django/views.mako", "uri": "django/views.mako", "source_encoding": "utf-8", "line_map": {"16": 4, "17": 5, "18": 6, "19": 7, "20": 0, "33": 1, "34": 6, "35": 7, "36": 8, "37": 9, "38": 10, "39": 11, "40": 12, "41": 13, "44": 12, "45": 20, "46": 20, "47": 22, "48": 22, "49": 23, "50": 23, "51": 24, "52": 24, "53": 25, "54": 25, "55": 28, "56": 29, "57": 31, "58": 33, "59": 34, "60": 35, "61": 35, "62": 36, "63": 36, "64": 37, "65": 37, "66": 38, "67": 38, "68": 40, "69": 40, "70": 43, "71": 43, "72": 46, "73": 47, "74": 48, "75": 49, "76": 50, "77": 51, "78": 52, "79": 53, "80": 54, "83": 53, "84": 54, "85": 54, "86": 56, "87": 56, "88": 56, "89": 58, "90": 58, "91": 60, "92": 60, "93": 63, "94": 63, "95": 66, "96": 66, "97": 69, "98": 69, "99": 77, "100": 77, "101": 83, "102": 84, "103": 85, "104": 85, "105": 86, "106": 86, "107": 87, "108": 87, "109": 89, "110": 89, "111": 90, "112": 90, "113": 92, "114": 92, "115": 95, "116": 96, "117": 97, "118": 98, "119": 99, "120": 100, "121": 101, "122": 102, "123": 103, "126": 102, "127": 103, "128": 103, "129": 105, "130": 105, "131": 105, "132": 105, "133": 105, "134": 107, "135": 107, "136": 110, "137": 110, "138": 110, "139": 110, "140": 113, "141": 113, "142": 113, "143": 113, "144": 115, "145": 115, "146": 117, "147": 117, "148": 120, "149": 120, "150": 124, "151": 124, "152": 130, "153": 131, "154": 132, "155": 132, "156": 133, "157": 133, "158": 134, "159": 134, "160": 135, "161": 135, "162": 137, "163": 137, "164": 138, "165": 138, "166": 141, "167": 141, "168": 144, "169": 145, "170": 146, "171": 147, "172": 148, "173": 149, "174": 150, "175": 151, "176": 152, "179": 151, "180": 152, "181": 152, "182": 154, "183": 154, "184": 154, "185": 154, "186": 154, "187": 156, "188": 156, "189": 159, "190": 159, "191": 159, "192": 159, "193": 162, "194": 162, "195": 162, "196": 162, "197": 164, "198": 164, "199": 167, "200": 167, "201": 175, "202": 175, "203": 178, "204": 178, "205": 186, "206": 186, "207": 192, "208": 193, "209": 194, "210": 194, "211": 195, "212": 195, "213": 196, "214": 196, "215": 199, "216": 199, "217": 201, "218": 201, "219": 204, "220": 205, "221": 206, "222": 207, "223": 208, "224": 209, "225": 210, "226": 211, "227": 212, "230": 211, "231": 212, "232": 212, "233": 214, "234": 214, "235": 214, "236": 214, "237": 214, "238": 216, "239": 216, "240": 219, "241": 219, "242": 219, "243": 219, "244": 222, "245": 222, "246": 222, "247": 222, "248": 224, "249": 224, "250": 227, "251": 227, "252": 230, "253": 230, "254": 236, "255": 237, "256": 238, "257": 238, "258": 239, "259": 239, "260": 240, "261": 240, "262": 242, "263": 242, "264": 244, "265": 244, "266": 247, "267": 248, "268": 249, "269": 250, "270": 251, "271": 252, "272": 253, "273": 254, "274": 255, "277": 254, "278": 255, "279": 255, "280": 257, "281": 257, "282": 257, "283": 259, "284": 259, "285": 264, "286": 264, "287": 269, "288": 270, "289": 271, "290": 272, "291": 272, "292": 272, "293": 274, "294": 275, "295": 276, "296": 281, "297": 284, "298": 285, "299": 285, "300": 285, "301": 286, "302": 287, "303": 289, "304": 293, "305": 294, "306": 299, "307": 299, "308": 313, "309": 313, "310": 315, "311": 316, "312": 316, "313": 316, "314": 319, "315": 319, "316": 322, "317": 324, "318": 324, "324": 318}}
__M_END_METADATA
"""

View File

@@ -0,0 +1,23 @@
"""
Django应用配置模板
"""
<%!
from datetime import datetime
%>
<%
# 生成应用配置类名
app_class_name = ''.join(word.capitalize() for word in app_name.split('_')) + 'Config'
%>
from django.apps import AppConfig
class ${app_class_name}(AppConfig):
"""
${app_name}应用配置
创建时间: ${datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
default_auto_field = 'django.db.models.BigAutoField'
name = '${app_name}'
verbose_name = '${verbose_name or app_name}'

View File

@@ -0,0 +1,173 @@
"""
Django模型模板
"""
<%!
from datetime import datetime
%>
<%
# 导入必要的模块
imports = []
if any(field.get('type') == 'ForeignKey' for field in fields):
imports.append('from django.contrib.auth import get_user_model')
if any(field.get('type') in ['DateTimeField', 'DateField', 'TimeField'] for field in fields):
imports.append('from django.utils import timezone')
if any(field.get('choices') for field in fields):
imports.append('from django.core.validators import validate_email')
# 生成状态选择项
status_choices_code = ""
if status_choices:
choices_list = []
for choice in status_choices:
if isinstance(choice, tuple) and len(choice) == 2:
choices_list.append(f" ('{choice[0]}', '{choice[1]}')")
else:
choices_list.append(f" ('{choice}', '{choice}')")
status_choices_code = f""" STATUS_CHOICES = [
{',{}'.format(chr(10)).join(choices_list)},
]"""
%>\
from django.db import models
% for import_line in imports:
${import_line}
% endfor
class ${model_name}(models.Model):
"""
${verbose_name or model_name}模型
% if table_name:
数据表名: ${table_name}
% endif
创建时间: ${datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
% if status_choices_code:
${status_choices_code}
% endif
% for field in fields:
<%
field_name = field['name']
# 使用转换后的Django字段类型
field_type = field.get('type', 'CharField')
field_options = []
# 处理字段选项
if field.get('verbose_name'):
field_options.append(f"verbose_name='{field['verbose_name']}'")
if field.get('help_text'):
field_options.append(f"help_text='{field['help_text']}'")
if field.get('max_length'):
field_options.append(f"max_length={field['max_length']}")
if field.get('null'):
field_options.append(f"null={field['null']}")
if field.get('blank'):
field_options.append(f"blank={field['blank']}")
if field.get('default') is not None:
if isinstance(field['default'], str):
field_options.append(f"default='{field['default']}'")
else:
field_options.append(f"default={field['default']}")
if field.get('unique'):
field_options.append(f"unique={field['unique']}")
if field.get('db_index'):
field_options.append(f"db_index={field['db_index']}")
# 处理特殊字段类型
if field_type == 'ForeignKey':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
else:
field_options.insert(0, "get_user_model()")
if field.get('on_delete'):
field_options.append(f"on_delete=models.{field['on_delete']}")
else:
field_options.append("on_delete=models.CASCADE")
elif field_type == 'ManyToManyField':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
elif field_type == 'OneToOneField':
if field.get('to'):
field_options.insert(0, f"'{field['to']}'")
if field.get('on_delete'):
field_options.append(f"on_delete=models.{field['on_delete']}")
else:
field_options.append("on_delete=models.CASCADE")
# 处理选择项
if field.get('choices'):
if field_name == 'status' and status_choices:
field_options.append("choices=STATUS_CHOICES")
else:
choices_list = []
for choice in field['choices']:
if isinstance(choice, tuple) and len(choice) == 2:
choices_list.append(f"('{choice[0]}', '{choice[1]}')")
else:
choices_list.append(f"('{choice}', '{choice}')")
field_options.append(f"choices=[{', '.join(choices_list)}]")
# 处理特殊字段类型的额外参数
if field_type == 'DecimalField':
if not any('max_digits' in opt for opt in field_options):
field_options.append('max_digits=10')
if not any('decimal_places' in opt for opt in field_options):
field_options.append('decimal_places=2')
elif field_type == 'DateTimeField':
if field_name == 'created_at' and not any('auto_now_add' in opt for opt in field_options):
field_options.append('auto_now_add=True')
elif field_name == 'updated_at' and not any('auto_now' in opt for opt in field_options):
field_options.append('auto_now=True')
options_str = ', '.join(field_options) if field_options else ''
%>
${field_name} = models.${field_type}(${options_str})
% endfor
class Meta:
% if table_name:
db_table = '${table_name}'
% endif
verbose_name = '${verbose_name or model_name}'
verbose_name_plural = '${verbose_name or model_name}'
% if ordering:
ordering = [${', '.join([f"'{field}'" for field in ordering])}]
% else:
ordering = ['-created_at']
% endif
def __str__(self):
"""字符串表示"""
% if fields:
<%
# 寻找合适的字段作为字符串表示
str_field = None
for field in fields:
if field['name'] in ['name', 'title', 'username', 'email']:
str_field = field['name']
break
if not str_field:
# 如果没有找到合适的字段,使用第一个字符串字段
for field in fields:
if field['type'] in ['CharField', 'TextField', 'EmailField']:
str_field = field['name']
break
if not str_field:
str_field = 'id'
%>
return str(self.${str_field})
% else:
return f"${model_name}({self.id})"
% endif
def save(self, *args, **kwargs):
"""保存方法"""
super().save(*args, **kwargs)
@classmethod
def get_by_id(cls, obj_id):
"""根据ID获取对象"""
try:
return cls.objects.get(id=obj_id)
except cls.DoesNotExist:
return None

View File

@@ -0,0 +1,62 @@
"""
Django主项目URL配置模板
用于自动添加新app的URL路由
"""
<%!
from datetime import datetime
%>
<%
# 获取现有的URL配置
existing_urls = url_patterns or []
new_app_config = {
'app_name': app_name,
'url_path': url_path or f'api/{app_name.replace("hertz_studio_django_", "").replace("_", "/")}/',
'comment': comment or f'{verbose_name or app_name} routes'
}
# 检查URL是否已存在
url_exists = any(config.get('app_name') == new_app_config['app_name'] for config in existing_urls)
if not url_exists:
existing_urls.append(new_app_config)
%>
"""
URL configuration for hertz_server_django project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from . import views
urlpatterns = [
# 首页路由
path('', views.index, name='index'),
% for url_config in existing_urls:
# ${url_config['comment']}
path('${url_config['url_path']}', include('${url_config['app_name']}.urls')),
% endfor
# API documentation routes
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
]
# 在开发环境下提供媒体文件服务
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0])

View File

@@ -0,0 +1,153 @@
"""
Django序列化器模板
"""
<%!
from datetime import datetime
%>
<%
# 生成字段列表
all_fields = []
create_fields_list = []
update_fields_list = []
list_fields_list = []
# 处理字段列表
for field in fields:
field_name = field['name']
all_fields.append(field_name)
# 排除自动生成的字段
if field_name not in ['id', 'created_at', 'updated_at']:
create_fields_list.append(field_name)
update_fields_list.append(field_name)
list_fields_list.append(field_name)
# 添加默认字段
if 'id' not in all_fields:
all_fields.insert(0, 'id')
if 'created_at' not in all_fields:
all_fields.append('created_at')
if 'updated_at' not in all_fields:
all_fields.append('updated_at')
# 如果没有指定列表字段,使用所有字段
if not list_fields_list:
list_fields_list = all_fields
%>
from rest_framework import serializers
from .models import ${model_name}
class ${model_name}Serializer(serializers.ModelSerializer):
"""
${model_name}序列化器
用于${model_name}模型的序列化和反序列化
创建时间: ${datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
class Meta:
model = ${model_name}
fields = [${', '.join([f"'{field}'" for field in all_fields])}]
read_only_fields = ['id', 'created_at', 'updated_at']
% for field in fields:
% if field.get('validators'):
def validate_${field['name']}(self, value):
"""
验证${field['name']}字段
"""
% for validator_name, validator_rule in field['validators'].items():
# ${validator_name}验证: ${validator_rule}
% endfor
return value
% endif
% endfor
def validate(self, attrs):
"""
对象级别的验证
"""
# 在这里添加跨字段验证逻辑
return attrs
def create(self, validated_data):
"""
创建${model_name}实例
"""
return ${model_name}.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
更新${model_name}实例
"""
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
class ${model_name}CreateSerializer(serializers.ModelSerializer):
"""
${model_name}创建序列化器
用于创建${model_name}实例
"""
class Meta:
model = ${model_name}
fields = [${', '.join([f"'{field}'" for field in create_fields_list])}]
% for field in fields:
% if field['name'] in create_fields_list and field.get('validators'):
def validate_${field['name']}(self, value):
"""
验证${field['name']}字段
"""
% for validator_name, validator_rule in field['validators'].items():
# ${validator_name}验证: ${validator_rule}
% endfor
return value
% endif
% endfor
class ${model_name}UpdateSerializer(serializers.ModelSerializer):
"""
${model_name}更新序列化器
用于更新${model_name}实例
"""
class Meta:
model = ${model_name}
fields = [${', '.join([f"'{field}'" for field in update_fields_list])}]
% for field in fields:
% if field['name'] in update_fields_list and field.get('validators'):
def validate_${field['name']}(self, value):
"""
验证${field['name']}字段
"""
% for validator_name, validator_rule in field['validators'].items():
# ${validator_name}验证: ${validator_rule}
% endfor
return value
% endif
% endfor
class ${model_name}ListSerializer(serializers.ModelSerializer):
"""
${model_name}列表序列化器
用于列表显示${model_name}实例
"""
class Meta:
model = ${model_name}
fields = [${', '.join([f"'{field}'" for field in list_fields_list])}]
read_only_fields = [${', '.join([f"'{field}'" for field in list_fields_list])}]

View File

@@ -0,0 +1,345 @@
"""
Django settings.py 配置模板
用于自动添加新app到INSTALLED_APPS
"""
<%!
from datetime import datetime
%>
<%
# 获取现有的INSTALLED_APPS列表
existing_apps = installed_apps or []
new_app = app_name
# 检查app是否已存在
if new_app not in existing_apps:
existing_apps.append(new_app)
%>
"""
Django settings for hertz_server_django project.
Generated by 'django-admin startproject' using Django 5.2.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from pathlib import Path
from decouple import config
# 修复DRF的ip_address_validators函数
def fix_drf_ip_validators():
"""
修复DRF的ip_address_validators函数返回值问题
"""
try:
from rest_framework import fields
# 保存原始函数
original_ip_address_validators = fields.ip_address_validators
def fixed_ip_address_validators(protocol, unpack_ipv4):
"""
修复后的ip_address_validators函数确保返回两个值
"""
validators = original_ip_address_validators(protocol, unpack_ipv4)
# 如果只返回了validators添加默认的error_message
if isinstance(validators, list):
return validators, 'Enter a valid IP address.'
else:
# 如果已经返回了两个值,直接返回
return validators
# 应用猴子补丁
fields.ip_address_validators = fixed_ip_address_validators
except ImportError:
# 如果DRF未安装忽略错误
pass
# 应用修复
fix_drf_ip_validators()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
# Database switch configuration
USE_REDIS_AS_DB = config('USE_REDIS_AS_DB', default=True, cast=bool)
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third party apps
'rest_framework',
'corsheaders',
'channels',
'drf_spectacular',
# Local apps
% for app in existing_apps:
'${app}',
% endfor
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'hertz_studio_django_auth.utils.middleware.AuthMiddleware', # 权限认证中间件
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'hertz_server_django.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'hertz_server_django.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
if USE_REDIS_AS_DB:
# Redis as primary database (for caching and session storage)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
# Use Redis for sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
else:
# MySQL database configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME', default='hertz_server'),
'USER': config('DB_USER', default='root'),
'PASSWORD': config('DB_PASSWORD', default='root'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='3306'),
'OPTIONS': {
'charset': 'utf8mb4',
},
}
}
# Redis
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/0'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# Media files (User uploaded files)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Django REST Framework configuration
# 使用自定义AuthMiddleware进行认证不使用DRF的认证和权限系统
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': [], # 不使用DRF认证类
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # 所有接口默认允许访问由AuthMiddleware控制权限
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
# Spectacular (OpenAPI 3.0) configuration
SPECTACULAR_SETTINGS = {
'TITLE': 'Hertz Server API',
'DESCRIPTION': 'API documentation for Hertz Server Django project',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SCHEMA_PATH_PREFIX': '/api/',
}
# CORS configuration
CORS_ALLOWED_ORIGINS = config(
'CORS_ALLOWED_ORIGINS',
default='http://localhost:3000,http://127.0.0.1:3000',
cast=lambda v: [s.strip() for s in v.split(',')]
)
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = config('CORS_ALLOW_ALL_ORIGINS', default=False, cast=bool)
# Captcha settings
CAPTCHA_IMAGE_SIZE = (
config('CAPTCHA_IMAGE_SIZE_WIDTH', default=120, cast=int),
config('CAPTCHA_IMAGE_SIZE_HEIGHT', default=50, cast=int)
)
CAPTCHA_LENGTH = config('CAPTCHA_LENGTH', default=4, cast=int)
CAPTCHA_TIMEOUT = config('CAPTCHA_TIMEOUT', default=5, cast=int) # minutes
CAPTCHA_FONT_SIZE = config('CAPTCHA_FONT_SIZE', default=40, cast=int)
CAPTCHA_BACKGROUND_COLOR = config('CAPTCHA_BACKGROUND_COLOR', default='#ffffff')
CAPTCHA_FOREGROUND_COLOR = config('CAPTCHA_FOREGROUND_COLOR', default='#000000')
# 验证码词典文件路径
CAPTCHA_WORDS_DICTIONARY = str(BASE_DIR / 'captcha_words.txt')
# 验证码挑战函数配置
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' # 默认使用随机字符
# 数学验证码配置
CAPTCHA_MATH_CHALLENGE_OPERATOR = '+-*'
# 验证码噪声和过滤器
CAPTCHA_NOISE_FUNCTIONS = (
'captcha.helpers.noise_arcs',
'captcha.helpers.noise_dots',
)
CAPTCHA_FILTER_FUNCTIONS = (
'captcha.helpers.post_smooth',
)
# Email configuration
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='smtp.qq.com')
EMAIL_PORT = config('EMAIL_PORT', default=465, cast=int)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=True, cast=bool)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='563161210@qq.com')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='563161210@qq.com')
# Channels configuration for WebSocket support
ASGI_APPLICATION = 'hertz_server_django.asgi.application'
# Channel layers configuration
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [config('REDIS_URL', default='redis://127.0.0.1:6379/2')],
},
},
}
# 自定义用户模型
AUTH_USER_MODEL = 'hertz_studio_django_auth.HertzUser'
# JWT配置
JWT_SECRET_KEY = config('JWT_SECRET_KEY', default=SECRET_KEY)
JWT_ALGORITHM = 'HS256'
JWT_ACCESS_TOKEN_LIFETIME = config('JWT_ACCESS_TOKEN_LIFETIME', default=60 * 60 * 24, cast=int) # 24小时
JWT_REFRESH_TOKEN_LIFETIME = config('JWT_REFRESH_TOKEN_LIFETIME', default=60 * 60 * 24 * 7, cast=int) # 7天
# 权限系统配置
HERTZ_AUTH_SETTINGS = {
'SUPER_ADMIN_PERMISSIONS': ['*'], # 超级管理员拥有所有权限
'DEFAULT_PERMISSIONS': [], # 默认权限
}
# AuthMiddleware配置 - 不需要登录验证的URL模式支持正则表达式
NO_AUTH_PATTERNS = config(
'NO_AUTH_PATTERNS',
default=r'^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$',
cast=lambda v: [s.strip() for s in v.split(',')]
)
# 密码加密配置
PASSWORD_HASHERS = [
'hertz_studio_django_utils.crypto.MD5PasswordHasher', # 使用MD5加密
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

View File

@@ -0,0 +1,56 @@
"""
Django URL配置模板
"""
<%!
from datetime import datetime
%>
<%
# 生成操作列表
operations_list = operations or ['create', 'get', 'update', 'delete', 'list']
snake_model_name = model_name.lower()
resource_name = snake_model_name
%>
from django.urls import path
from . import views
app_name = '${app_name or snake_model_name}'
urlpatterns = [
% if 'create' in operations_list:
# 创建${model_name}
path('', views.create_${snake_model_name}, name='create_${snake_model_name}'),
% endif
% if 'list' in operations_list:
# 获取${model_name}列表
path('list/', views.list_${snake_model_name}, name='list_${snake_model_name}'),
% endif
% if 'get' in operations_list:
# 获取${model_name}详情
path('<int:${snake_model_name}_id>/', views.get_${snake_model_name}, name='get_${snake_model_name}'),
% endif
% if 'update' in operations_list:
# 更新${model_name}
path('<int:${snake_model_name}_id>/update/', views.update_${snake_model_name}, name='update_${snake_model_name}'),
% endif
% if 'delete' in operations_list:
# 删除${model_name}
path('<int:${snake_model_name}_id>/delete/', views.delete_${snake_model_name}, name='delete_${snake_model_name}'),
% endif
]
# RESTful风格的URL配置可选
restful_urlpatterns = [
% if 'create' in operations_list or 'list' in operations_list:
# POST: 创建${model_name}, GET: 获取${model_name}列表
path('${prefix}${resource_name}/', views.${'create_' + snake_model_name if 'create' in operations_list else 'list_' + snake_model_name}, name='${snake_model_name}_collection'),
% endif
% if 'get' in operations_list or 'update' in operations_list or 'delete' in operations_list:
# GET: 获取详情, PUT/PATCH: 更新, DELETE: 删除
path('${prefix}${resource_name}/<int:${snake_model_name}_id>/', views.update_${snake_model_name}, name='${snake_model_name}_detail'),
% endif
]

View File

@@ -0,0 +1,329 @@
"""
Django视图模板
"""
<%!
from datetime import datetime
%>
<%
# 生成操作列表
operations_list = operations or ['create', 'get', 'update', 'delete', 'list']
permissions_list = permissions or []
snake_model_name = model_name.lower()
%>
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from drf_spectacular.utils import extend_schema, OpenApiResponse
from django.core.paginator import Paginator
from django.db.models import Q
from .models import ${model_name}
from .serializers import (
${model_name}Serializer,
${model_name}CreateSerializer,
${model_name}UpdateSerializer,
${model_name}ListSerializer
)
from hertz_studio_django_utils.responses import HertzResponse
% if permissions_list:
from hertz_studio_django_auth.utils.decorators import login_required, permission_required
% endif
% if 'create' in operations_list:
@extend_schema(
operation_id='create_${snake_model_name}',
summary='创建${model_name}',
description='创建新的${model_name}实例',
request=${model_name}CreateSerializer,
responses={
201: OpenApiResponse(response=${model_name}Serializer, description='创建成功'),
400: OpenApiResponse(description='参数错误'),
},
tags=['${model_name}']
)
@api_view(['POST'])
% if 'create' in permissions_list:
<%
# 获取权限代码如果permissions_list是字典则获取对应的权限代码
if isinstance(permissions_list, dict) and 'create' in permissions_list:
permission_code = permissions_list['create']
else:
permission_code = f'{snake_model_name}:create'
%>
@permission_required('${permission_code}')
% endif
def create_${snake_model_name}(request):
"""
创建${model_name}
创建时间: ${datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
try:
serializer = ${model_name}CreateSerializer(data=request.data)
if serializer.is_valid():
instance = serializer.save()
response_serializer = ${model_name}Serializer(instance)
return HertzResponse.success(
data=response_serializer.data,
message='${model_name}创建成功'
)
return HertzResponse.validation_error(
message='参数验证失败',
errors=serializer.errors
)
except Exception as e:
return HertzResponse.error(
message='创建${model_name}失败',
error=str(e)
)
% endif
% if 'get' in operations_list or 'retrieve' in operations_list:
@extend_schema(
operation_id='get_${snake_model_name}',
summary='获取${model_name}详情',
description='根据ID获取${model_name}详情',
responses={
200: OpenApiResponse(response=${model_name}Serializer, description='获取成功'),
404: OpenApiResponse(description='${model_name}不存在'),
},
tags=['${model_name}']
)
@api_view(['GET'])
% if 'get' in permissions_list or 'retrieve' in permissions_list:
<%
# 获取权限代码
if isinstance(permissions_list, dict):
permission_code = permissions_list.get('get') or permissions_list.get('retrieve') or f'{snake_model_name}:query'
else:
permission_code = f'{snake_model_name}:query'
%>
@permission_required('${permission_code}')
% endif
def get_${snake_model_name}(request, ${snake_model_name}_id):
"""
获取${model_name}详情
Args:
${snake_model_name}_id: ${model_name}ID
"""
try:
instance = ${model_name}.get_by_id(${snake_model_name}_id)
if not instance:
return HertzResponse.not_found(message='${model_name}不存在')
serializer = ${model_name}Serializer(instance)
return HertzResponse.success(
data=serializer.data,
message='获取${model_name}详情成功'
)
except Exception as e:
return HertzResponse.error(
message='获取${model_name}详情失败',
error=str(e)
)
% endif
% if 'update' in operations_list:
@extend_schema(
operation_id='update_${snake_model_name}',
summary='更新${model_name}',
description='根据ID更新${model_name}信息',
request=${model_name}UpdateSerializer,
responses={
200: OpenApiResponse(response=${model_name}Serializer, description='更新成功'),
404: OpenApiResponse(description='${model_name}不存在'),
400: OpenApiResponse(description='参数错误'),
},
tags=['${model_name}']
)
@api_view(['PUT', 'PATCH'])
% if 'update' in permissions_list:
<%
# 获取权限代码
if isinstance(permissions_list, dict) and 'update' in permissions_list:
permission_code = permissions_list['update']
else:
permission_code = f'{snake_model_name}:update'
%>
@permission_required('${permission_code}')
% endif
def update_${snake_model_name}(request, ${snake_model_name}_id):
"""
更新${model_name}
Args:
${snake_model_name}_id: ${model_name}ID
"""
try:
instance = ${model_name}.get_by_id(${snake_model_name}_id)
if not instance:
return HertzResponse.not_found(message='${model_name}不存在')
partial = request.method == 'PATCH'
serializer = ${model_name}UpdateSerializer(
instance,
data=request.data,
partial=partial
)
if serializer.is_valid():
updated_instance = serializer.save()
response_serializer = ${model_name}Serializer(updated_instance)
return HertzResponse.success(
data=response_serializer.data,
message='${model_name}更新成功'
)
return HertzResponse.validation_error(
message='参数验证失败',
errors=serializer.errors
)
except Exception as e:
return HertzResponse.error(
message='更新${model_name}失败',
error=str(e)
)
% endif
% if 'delete' in operations_list:
@extend_schema(
operation_id='delete_${snake_model_name}',
summary='删除${model_name}',
description='根据ID删除${model_name}',
responses={
200: OpenApiResponse(description='删除成功'),
404: OpenApiResponse(description='${model_name}不存在'),
},
tags=['${model_name}']
)
@api_view(['DELETE'])
% if 'delete' in permissions_list:
<%
# 获取权限代码
if isinstance(permissions_list, dict) and 'delete' in permissions_list:
permission_code = permissions_list['delete']
else:
permission_code = f'{snake_model_name}:delete'
%>
@permission_required('${permission_code}')
% endif
def delete_${snake_model_name}(request, ${snake_model_name}_id):
"""
删除${model_name}
Args:
${snake_model_name}_id: ${model_name}ID
"""
try:
instance = ${model_name}.get_by_id(${snake_model_name}_id)
if not instance:
return HertzResponse.not_found(message='${model_name}不存在')
instance.delete()
return HertzResponse.success(message='${model_name}删除成功')
except Exception as e:
return HertzResponse.error(
message='删除${model_name}失败',
error=str(e)
)
% endif
% if 'list' in operations_list:
@extend_schema(
operation_id='list_${snake_model_name}',
summary='获取${model_name}列表',
description='分页获取${model_name}列表',
responses={
200: OpenApiResponse(response=${model_name}ListSerializer, description='获取成功'),
},
tags=['${model_name}']
)
@api_view(['GET'])
% if 'list' in permissions_list:
<%
# 获取权限代码
if isinstance(permissions_list, dict) and 'list' in permissions_list:
permission_code = permissions_list['list']
else:
permission_code = f'{snake_model_name}:list'
%>
@permission_required('${permission_code}')
% endif
def list_${snake_model_name}(request):
"""
获取${model_name}列表
支持分页、搜索和排序
"""
try:
queryset = ${model_name}.objects.all()
# 搜索功能
search = request.GET.get('search', '')
if search:
% if search_fields:
search_q = Q()
% for field in search_fields:
search_q |= Q(${field}__icontains=search)
% endfor
queryset = queryset.filter(search_q)
% else:
# 默认搜索字段,可根据需要调整
queryset = queryset.filter(
Q(id__icontains=search)
)
% endif
# 排序功能
ordering = request.GET.get('ordering', '-created_at')
% if ordering:
valid_orderings = [${', '.join([f"'{field}'" for field in ordering] + [f"'-{field}'" for field in ordering])}]
% else:
valid_orderings = ['created_at', '-created_at', 'updated_at', '-updated_at']
% endif
if ordering in valid_orderings:
queryset = queryset.order_by(ordering)
# 分页功能
% if pagination:
page = int(request.GET.get('page', 1))
page_size = int(request.GET.get('page_size', 20))
paginator = Paginator(queryset, page_size)
page_obj = paginator.get_page(page)
serializer = ${model_name}ListSerializer(page_obj.object_list, many=True)
return HertzResponse.success(
data={
'results': serializer.data,
'pagination': {
'page': page,
'page_size': page_size,
'total_pages': paginator.num_pages,
'total_count': paginator.count,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
}
},
message='获取${model_name}列表成功'
)
% else:
serializer = ${model_name}ListSerializer(queryset, many=True)
return HertzResponse.success(
data=serializer.data,
message='获取${model_name}列表成功'
)
% endif
except Exception as e:
return HertzResponse.error(
message='获取${model_name}列表失败',
error=str(e)
)
% endif

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class hertz_studio_django_testConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hertz_studio_django_test'
verbose_name = '测试应用'

View File

@@ -0,0 +1,30 @@
from django.db import models
from hertz_studio_django_auth.models import HertzUser
import os
class Testmodels(models.Model):
"""
测试模型
"""
# 测试字段
test_field = models.CharField(max_length=100, verbose_name='测试字段')
name = models.CharField(max_length=50, unique=True, verbose_name='名称')
test_path = models.CharField(max_length=100, verbose_name='测试路径')
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='父项')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_at = models.DatetimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = test1
verbose_name = '测试模型'
verbose_name_plural = '测试模型'
def __str__(self):
return self.name
def get_full_path(self):
if self.parent:
return f'{self.parent.get_full_path()} > {self.name}'
return self.name

View File

@@ -0,0 +1,24 @@
from rest_framework import serializers
from django.utils import timezone
from models import *
class TestSerializer(serializers.ModelSerializer):
"""
测试序列化器
"""
parent_name = serializers.CharField(source='parent.name', read_only=True)
children_count = serializers.SerializerMethodField()
articales_count = serializers.SerializerMethodField()
full_path = serializers.CharField(source='get_full_path',read_only=True)
class Meta:
model = Testmodels
fields=[
'name','parent','is_active','full_path','created_at','updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
def get_children_count(self, obj):
"""获取子分类数量"""
return obj.children.filter(is_active=True).count()

View File

@@ -0,0 +1,6 @@
from django.urls import path, include
from views import *
urlpatterns = [
path('test/', test, name='test'),
]

View File

@@ -0,0 +1,350 @@
"""
Django URL配置代码生成器
该模块负责根据配置生成Django URL路由代码
"""
import os
from typing import Dict, List, Any, Optional
from .base_generator import BaseGenerator
class URLGenerator(BaseGenerator):
"""Django URL配置代码生成器"""
def __init__(self):
"""初始化URL生成器"""
super().__init__()
def generate(self, model_name: str = None, operations: List[str] = None, app_name: str = None, **kwargs) -> str:
"""
生成Django URL配置代码
Args:
model_name: 模型名称
operations: 支持的操作列表
app_name: 应用名称
**kwargs: 其他参数
Returns:
str: 生成的URL配置代码
"""
# 处理参数,支持位置参数和关键字参数
if model_name is None:
model_name = kwargs.get('model_name', 'DefaultModel')
if operations is None:
operations = kwargs.get('operations', ['list', 'create', 'retrieve', 'update', 'delete'])
if app_name is None:
app_name = kwargs.get('app_name', 'default_app')
api_version = kwargs.get('api_version', 'v1')
prefix = kwargs.get('prefix', '')
namespace = kwargs.get('namespace', app_name)
# 确保operations是列表
if isinstance(operations, str):
operations = [operations]
# 准备模板上下文
context = {
'app_name': app_name,
'model_name': model_name,
'model_name_lower': model_name.lower(),
'snake_model_name': model_name.lower(), # 添加snake_model_name变量
'operations': operations,
'api_version': api_version,
'prefix': prefix,
'namespace': namespace,
'has_create': 'create' in operations,
'has_list': 'list' in operations,
'has_retrieve': 'retrieve' in operations,
'has_update': 'update' in operations,
'has_delete': 'delete' in operations,
'url_patterns': []
}
# 生成URL模式
for operation in operations:
if operation == 'list':
context['url_patterns'].append({
'pattern': f'{model_name.lower()}/',
'view': f'list_{model_name.lower()}',
'name': f'{model_name.lower()}_list'
})
elif operation == 'create':
context['url_patterns'].append({
'pattern': f'{model_name.lower()}/create/',
'view': f'create_{model_name.lower()}',
'name': f'{model_name.lower()}_create'
})
elif operation == 'retrieve' or operation == 'get':
context['url_patterns'].append({
'pattern': f'{model_name.lower()}/<int:pk>/',
'view': f'get_{model_name.lower()}',
'name': f'{model_name.lower()}_detail'
})
elif operation == 'update':
context['url_patterns'].append({
'pattern': f'{model_name.lower()}/<int:pk>/update/',
'view': f'update_{model_name.lower()}',
'name': f'{model_name.lower()}_update'
})
elif operation == 'delete':
context['url_patterns'].append({
'pattern': f'{model_name.lower()}/<int:pk>/delete/',
'view': f'delete_{model_name.lower()}',
'name': f'{model_name.lower()}_delete'
})
# 渲染模板
return self.render_template('django/urls.mako', context)
def generate_rest_urls(
self,
app_name: str,
model_name: str,
viewset_name: Optional[str] = None,
api_version: str = 'v1',
namespace: Optional[str] = None
) -> str:
"""
生成REST风格的URL配置
Args:
app_name: 应用名称
model_name: 模型名称
viewset_name: ViewSet名称
api_version: API版本
namespace: 命名空间
Returns:
str: 生成的REST URL配置代码
"""
if viewset_name is None:
viewset_name = f'{model_name}ViewSet'
context = {
'app_name': app_name,
'model_name': model_name,
'viewset_name': viewset_name,
'api_version': api_version,
'namespace': namespace,
'url_type': 'rest'
}
return self.render_template('django/urls.mako', context)
def generate_router_urls(
self,
app_name: str,
viewsets: List[Dict[str, str]],
api_version: str = 'v1',
router_type: str = 'DefaultRouter'
) -> str:
"""
生成使用Router的URL配置
Args:
app_name: 应用名称
viewsets: ViewSet配置列表
api_version: API版本
router_type: Router类型
Returns:
str: 生成的Router URL配置代码
"""
context = {
'app_name': app_name,
'viewsets': viewsets,
'api_version': api_version,
'router_type': router_type,
'url_type': 'router'
}
return self.render_template('django/urls.mako', context)
def generate_function_based_urls(
self,
app_name: str,
views: List[Dict[str, Any]],
api_version: str = 'v1'
) -> str:
"""
生成基于函数视图的URL配置
Args:
app_name: 应用名称
views: 视图配置列表
api_version: API版本
Returns:
str: 生成的函数视图URL配置代码
"""
context = {
'app_name': app_name,
'views': views,
'api_version': api_version,
'url_type': 'function_based'
}
return self.render_template('django/urls.mako', context)
def generate_nested_urls(
self,
app_name: str,
parent_model: str,
child_model: str,
operations: List[str],
api_version: str = 'v1'
) -> str:
"""
生成嵌套资源的URL配置
Args:
app_name: 应用名称
parent_model: 父模型名称
child_model: 子模型名称
operations: 支持的操作列表
api_version: API版本
Returns:
str: 生成的嵌套URL配置代码
"""
context = {
'app_name': app_name,
'parent_model': parent_model,
'child_model': child_model,
'operations': operations,
'api_version': api_version,
'url_type': 'nested'
}
return self.render_template('django/urls.mako', context)
def generate_custom_action_urls(
self,
app_name: str,
model_name: str,
actions: List[Dict[str, Any]],
api_version: str = 'v1'
) -> str:
"""
生成自定义动作的URL配置
Args:
app_name: 应用名称
model_name: 模型名称
actions: 自定义动作配置列表
api_version: API版本
Returns:
str: 生成的自定义动作URL配置代码
"""
context = {
'app_name': app_name,
'model_name': model_name,
'actions': actions,
'api_version': api_version,
'url_type': 'custom_actions'
}
return self.render_template('django/urls.mako', context)
def generate_app_urls(
self,
app_name: str,
models: List[str],
api_version: str = 'v1',
include_admin: bool = False
) -> str:
"""
生成应用级别的URL配置
Args:
app_name: 应用名称
models: 模型名称列表
api_version: API版本
include_admin: 是否包含管理后台URL
Returns:
str: 生成的应用URL配置代码
"""
context = {
'app_name': app_name,
'models': models,
'api_version': api_version,
'include_admin': include_admin,
'url_type': 'app_level'
}
return self.render_template('django/urls.mako', context)
def add_url_pattern(
self,
url_code: str,
pattern: str,
view: str,
name: str,
methods: Optional[List[str]] = None
) -> str:
"""
添加URL模式
Args:
url_code: 原URL配置代码
pattern: URL模式
view: 视图名称
name: URL名称
methods: HTTP方法列表
Returns:
str: 包含新URL模式的配置代码
"""
if methods:
method_str = f", methods={methods}"
else:
method_str = ""
new_pattern = f" path('{pattern}', {view}, name='{name}'{method_str}),"
# 在urlpatterns列表中添加新模式
lines = url_code.split('\n')
for i, line in enumerate(lines):
if 'urlpatterns = [' in line:
# 找到列表结束位置
for j in range(i + 1, len(lines)):
if lines[j].strip() == ']':
lines.insert(j, new_pattern)
break
break
return '\n'.join(lines)
def generate_api_documentation_urls(
self,
app_name: str,
api_version: str = 'v1',
include_swagger: bool = True,
include_redoc: bool = True
) -> str:
"""
生成API文档的URL配置
Args:
app_name: 应用名称
api_version: API版本
include_swagger: 是否包含Swagger UI
include_redoc: 是否包含ReDoc
Returns:
str: 生成的API文档URL配置代码
"""
context = {
'app_name': app_name,
'api_version': api_version,
'include_swagger': include_swagger,
'include_redoc': include_redoc,
'url_type': 'api_docs'
}
return self.render_template('django/urls.mako', context)

View File

@@ -0,0 +1,416 @@
"""
Django视图代码生成器
该模块负责根据配置生成Django REST Framework视图代码
"""
import os
from typing import Dict, List, Any, Optional
from .base_generator import BaseGenerator
class ViewGenerator(BaseGenerator):
"""Django视图代码生成器"""
def __init__(self):
"""初始化视图生成器"""
super().__init__()
def generate(self, model_name: str = None, operations: List[str] = None, **kwargs) -> str:
"""
生成Django视图代码
Args:
model_name: 模型名称
operations: 支持的操作列表
**kwargs: 其他参数
Returns:
str: 生成的视图代码
"""
# 处理参数,支持位置参数和关键字参数
if model_name is None:
model_name = kwargs.get('model_name', 'DefaultModel')
if operations is None:
operations = kwargs.get('operations', ['list', 'create', 'retrieve', 'update', 'delete'])
permissions = kwargs.get('permissions', {})
authentication = kwargs.get('authentication', ['IsAuthenticated'])
pagination = kwargs.get('pagination', True)
filters = kwargs.get('filters', {})
permission_type = kwargs.get('permission_type', 'standard') # 'standard' 或 'system'
# 确保operations是列表
if isinstance(operations, str):
operations = [operations]
# 自动生成权限配置
if not permissions and permission_type:
if permission_type == 'system':
permissions = self.generate_system_permission_config(model_name, operations)
else:
permissions = self.generate_permission_config(model_name, operations)
# 准备模板上下文
context = {
'model_name': model_name,
'model_name_lower': model_name.lower(),
'operations': operations,
'permissions': permissions,
'permissions_list': permissions, # 为了兼容模板
'authentication': authentication,
'pagination': pagination,
'filters': filters,
'has_create': 'create' in operations,
'has_list': 'list' in operations,
'has_retrieve': 'retrieve' in operations,
'has_update': 'update' in operations,
'has_delete': 'delete' in operations,
'viewset_name': f'{model_name}ViewSet',
'serializer_name': f'{model_name}Serializer',
'queryset_name': f'{model_name}.objects.all()',
'view_classes': []
}
# 生成视图类列表
for operation in operations:
if operation == 'list':
context['view_classes'].append({
'name': f'{model_name}ListView',
'base_class': 'ListAPIView',
'operation': 'list'
})
elif operation == 'create':
context['view_classes'].append({
'name': f'{model_name}CreateView',
'base_class': 'CreateAPIView',
'operation': 'create'
})
elif operation == 'retrieve' or operation == 'get':
context['view_classes'].append({
'name': f'{model_name}DetailView',
'base_class': 'RetrieveAPIView',
'operation': 'retrieve'
})
elif operation == 'update':
context['view_classes'].append({
'name': f'{model_name}UpdateView',
'base_class': 'UpdateAPIView',
'operation': 'update'
})
elif operation == 'delete':
context['view_classes'].append({
'name': f'{model_name}DeleteView',
'base_class': 'DestroyAPIView',
'operation': 'delete'
})
# 渲染模板
return self.render_template('django/views.mako', context)
def generate_api_view(
self,
model_name: str,
view_name: str,
http_methods: List[str],
permissions: Optional[List[str]] = None,
authentication: Optional[List[str]] = None
) -> str:
"""
生成API视图代码
Args:
model_name: 模型名称
view_name: 视图名称
http_methods: HTTP方法列表
permissions: 权限列表
authentication: 认证方式列表
Returns:
str: 生成的API视图代码
"""
context = {
'model_name': model_name,
'view_name': view_name,
'http_methods': http_methods,
'permissions': permissions or [],
'authentication': authentication or [],
'view_type': 'api_view'
}
return self.render_template('django/views.mako', context)
def generate_viewset(
self,
model_name: str,
viewset_type: str = 'ModelViewSet',
operations: Optional[List[str]] = None,
permissions: Optional[List[str]] = None,
filters: Optional[Dict[str, Any]] = None
) -> str:
"""
生成ViewSet代码
Args:
model_name: 模型名称
viewset_type: ViewSet类型
operations: 支持的操作列表
permissions: 权限列表
filters: 过滤器配置
Returns:
str: 生成的ViewSet代码
"""
if operations is None:
operations = ['list', 'create', 'retrieve', 'update', 'destroy']
context = {
'model_name': model_name,
'viewset_type': viewset_type,
'operations': operations,
'permissions': permissions or [],
'filters': filters or {},
'view_type': 'viewset'
}
return self.render_template('django/views.mako', context)
def generate_generic_view(
self,
model_name: str,
view_type: str,
permissions: Optional[List[str]] = None,
filters: Optional[Dict[str, Any]] = None
) -> str:
"""
生成通用视图代码
Args:
model_name: 模型名称
view_type: 视图类型 (ListAPIView, CreateAPIView等)
permissions: 权限列表
filters: 过滤器配置
Returns:
str: 生成的通用视图代码
"""
context = {
'model_name': model_name,
'view_type': view_type,
'permissions': permissions or [],
'filters': filters or {},
'generic_view': True
}
return self.render_template('django/views.mako', context)
def generate_crud_views(
self,
model_name: str,
operations: List[str],
permissions: Optional[Dict[str, List[str]]] = None,
pagination: bool = True,
filters: Optional[Dict[str, Any]] = None
) -> str:
"""
生成CRUD视图代码
Args:
model_name: 模型名称
operations: 支持的操作列表
permissions: 每个操作的权限配置
pagination: 是否启用分页
filters: 过滤器配置
Returns:
str: 生成的CRUD视图代码
"""
context = {
'model_name': model_name,
'operations': operations,
'permissions': permissions or {},
'pagination': pagination,
'filters': filters or {},
'crud_views': True
}
return self.render_template('django/views.mako', context)
def generate_custom_action(
self,
action_name: str,
http_methods: List[str],
detail: bool = False,
permissions: Optional[List[str]] = None,
serializer_class: Optional[str] = None
) -> str:
"""
生成自定义动作代码
Args:
action_name: 动作名称
http_methods: HTTP方法列表
detail: 是否为详情动作
permissions: 权限列表
serializer_class: 序列化器类名
Returns:
str: 生成的自定义动作代码
"""
context = {
'action_name': action_name,
'http_methods': http_methods,
'detail': detail,
'permissions': permissions or [],
'serializer_class': serializer_class,
'custom_action': True
}
return self.render_template('django/views.mako', context)
def add_permission_decorator(
self,
view_code: str,
permissions: List[str]
) -> str:
"""
添加权限装饰器
Args:
view_code: 原视图代码
permissions: 权限列表
Returns:
str: 包含权限装饰器的视图代码
"""
decorators = []
for permission in permissions:
if permission == 'login_required':
decorators.append('@login_required')
elif permission == 'no_login_required':
decorators.append('@no_login_required')
else:
decorators.append(f'@permission_required("{permission}")')
# 在函数定义前添加装饰器
lines = view_code.split('\n')
for i, line in enumerate(lines):
if line.strip().startswith('def ') or line.strip().startswith('class '):
# 在函数或类定义前插入装饰器
for j, decorator in enumerate(decorators):
lines.insert(i + j, decorator)
break
return '\n'.join(lines)
def generate_permission_config(
self,
model_name: str,
operations: List[str],
permission_prefix: str = None
) -> Dict[str, str]:
"""
生成权限配置字典
Args:
model_name: 模型名称
operations: 操作列表
permission_prefix: 权限前缀,默认使用模型名称小写
Returns:
Dict[str, str]: 权限配置字典
"""
if permission_prefix is None:
permission_prefix = model_name.lower()
# 统一权限映射规则,与菜单生成器保持一致
permission_mapping = {
'list': f'{permission_prefix}:list',
'create': f'{permission_prefix}:add', # 改为add与菜单一致
'retrieve': f'{permission_prefix}:list', # 改为list与菜单一致
'get': f'{permission_prefix}:list', # 改为list与菜单一致
'update': f'{permission_prefix}:edit', # 改为edit与菜单一致
'delete': f'{permission_prefix}:remove', # 改为remove与菜单一致
'partial_update': f'{permission_prefix}:edit' # 改为edit与菜单一致
}
return {op: permission_mapping.get(op, f'{permission_prefix}:{op}') for op in operations}
def generate_system_permission_config(
self,
model_name: str,
operations: List[str],
module_prefix: str = 'system'
) -> Dict[str, str]:
"""
生成系统级权限配置字典
Args:
model_name: 模型名称
operations: 操作列表
module_prefix: 模块前缀,默认为'system'
Returns:
Dict[str, str]: 系统级权限配置字典
"""
model_lower = model_name.lower()
permission_mapping = {
'list': f'{module_prefix}:{model_lower}:list',
'create': f'{model_lower}:create',
'retrieve': f'{module_prefix}:{model_lower}:query',
'get': f'{module_prefix}:{model_lower}:query',
'update': f'{model_lower}:update',
'delete': f'{model_lower}:delete',
'partial_update': f'{model_lower}:update'
}
return {op: permission_mapping.get(op, f'{module_prefix}:{model_lower}:{op}') for op in operations}
def add_swagger_documentation(
self,
view_code: str,
operation_id: str,
summary: str,
description: str,
tags: List[str],
request_schema: Optional[str] = None,
response_schema: Optional[str] = None
) -> str:
"""
添加Swagger文档注解
Args:
view_code: 原视图代码
operation_id: 操作ID
summary: 摘要
description: 描述
tags: 标签列表
request_schema: 请求模式
response_schema: 响应模式
Returns:
str: 包含Swagger文档的视图代码
"""
swagger_decorator = f"""@extend_schema(
operation_id='{operation_id}',
summary='{summary}',
description='{description}',
tags={tags}"""
if request_schema:
swagger_decorator += f",\n request={request_schema}"
if response_schema:
swagger_decorator += f",\n responses={{200: {response_schema}}}"
swagger_decorator += "\n)"
# 在函数定义前添加装饰器
lines = view_code.split('\n')
for i, line in enumerate(lines):
if line.strip().startswith('def '):
lines.insert(i, swagger_decorator)
break
return '\n'.join(lines)

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env python
"""
YAML Django代码生成器命令行工具
该脚本提供命令行接口用于从YAML配置文件生成Django应用代码。
使用示例:
python yaml_generator_cli.py generate app_config.yaml
python yaml_generator_cli.py template --output my_app.yaml
python yaml_generator_cli.py validate app_config.yaml
"""
import argparse
import sys
import os
from pathlib import Path
from django_code_generator import DjangoCodeGenerator
def generate_from_yaml(yaml_file_path: str, output_dir: str = None) -> bool:
"""
从YAML文件生成Django应用代码
Args:
yaml_file_path: YAML配置文件路径
output_dir: 输出目录(可选)
Returns:
bool: 生成是否成功
"""
try:
generator = DjangoCodeGenerator()
# 解析YAML配置
config = generator.yaml_parser.parse_yaml_file(yaml_file_path)
# 如果指定了输出目录,覆盖配置中的设置
if output_dir:
config['output_dir'] = output_dir
print(f"正在从 {yaml_file_path} 生成Django应用代码...")
print(f"应用名称: {config['app_name']}")
print(f"输出目录: {config['output_dir']}")
print(f"模型数量: {len(config['models'])}")
# 生成代码
generated_files = generator.generate_from_yaml_config(config)
print(f"\n✅ 成功生成 {len(generated_files)} 个文件:")
for file_path in sorted(generated_files.keys()):
print(f" 📄 {file_path}")
print(f"\n🎉 Django应用 '{config['app_name']}' 生成完成!")
print(f"📁 输出目录: {os.path.abspath(config['output_dir'])}")
return True
except FileNotFoundError as e:
print(f"❌ 错误: {e}")
return False
except Exception as e:
print(f"❌ 生成失败: {e}")
return False
def generate_template(output_path: str = 'app_template.yaml') -> bool:
"""
生成YAML配置文件模板
Args:
output_path: 输出文件路径
Returns:
bool: 生成是否成功
"""
try:
generator = DjangoCodeGenerator()
template_content = generator.generate_yaml_template(output_path)
print(f"✅ YAML配置模板已生成: {os.path.abspath(output_path)}")
print("\n📝 模板内容预览:")
print("-" * 50)
# 显示前20行
lines = template_content.split('\n')
for i, line in enumerate(lines[:20]):
print(f"{i+1:2d}: {line}")
if len(lines) > 20:
print(f"... (还有 {len(lines) - 20} 行)")
print("-" * 50)
return True
except Exception as e:
print(f"❌ 模板生成失败: {e}")
return False
def validate_yaml(yaml_file_path: str) -> bool:
"""
验证YAML配置文件
Args:
yaml_file_path: YAML配置文件路径
Returns:
bool: 验证是否通过
"""
try:
generator = DjangoCodeGenerator()
config = generator.yaml_parser.parse_yaml_file(yaml_file_path)
print(f"✅ YAML配置文件验证通过: {yaml_file_path}")
print(f"📋 配置摘要:")
print(f" 应用名称: {config['app_name']}")
print(f" 版本: {config.get('version', 'N/A')}")
print(f" 描述: {config.get('description', 'N/A')}")
print(f" 作者: {config.get('author', 'N/A')}")
print(f" 输出目录: {config['output_dir']}")
print(f" 模型数量: {len(config['models'])}")
print(f"\n📊 模型详情:")
for i, model in enumerate(config['models'], 1):
print(f" {i}. {model['name']} ({len(model['fields'])} 个字段)")
operations = model.get('operations', [])
if operations:
print(f" 操作: {', '.join(operations)}")
return True
except Exception as e:
print(f"❌ YAML配置验证失败: {e}")
return False
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description='YAML Django代码生成器',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
%(prog)s generate app_config.yaml # 从YAML生成代码
%(prog)s generate app_config.yaml -o ./my_app # 指定输出目录
%(prog)s template # 生成默认模板
%(prog)s template -o my_template.yaml # 生成自定义模板
%(prog)s validate app_config.yaml # 验证YAML配置
"""
)
subparsers = parser.add_subparsers(dest='command', help='可用命令')
# generate 子命令
generate_parser = subparsers.add_parser('generate', help='从YAML文件生成Django应用代码')
generate_parser.add_argument('yaml_file', help='YAML配置文件路径')
generate_parser.add_argument('-o', '--output', help='输出目录')
# template 子命令
template_parser = subparsers.add_parser('template', help='生成YAML配置文件模板')
template_parser.add_argument('-o', '--output', default='app_template.yaml', help='输出文件路径')
# validate 子命令
validate_parser = subparsers.add_parser('validate', help='验证YAML配置文件')
validate_parser.add_argument('yaml_file', help='YAML配置文件路径')
args = parser.parse_args()
if not args.command:
parser.print_help()
return 1
success = False
if args.command == 'generate':
success = generate_from_yaml(args.yaml_file, args.output)
elif args.command == 'template':
success = generate_template(args.output)
elif args.command == 'validate':
success = validate_yaml(args.yaml_file)
return 0 if success else 1
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,372 @@
"""
YAML配置文件解析器
该模块负责解析YAML配置文件将其转换为Django代码生成器可以使用的数据结构。
使用示例:
parser = YAMLParser()
config = parser.parse_yaml_file('app_config.yaml')
generator = DjangoCodeGenerator()
generator.generate_from_yaml_config(config)
"""
import yaml
import os
from typing import Dict, List, Any, Optional
from pathlib import Path
class YAMLParser:
"""YAML配置文件解析器"""
def __init__(self):
"""初始化YAML解析器"""
# 支持Django字段类型
self.supported_field_types = {
'CharField', 'TextField', 'IntegerField', 'FloatField',
'DecimalField', 'BooleanField', 'DateField', 'DateTimeField',
'EmailField', 'URLField', 'ImageField', 'FileField',
'ForeignKey', 'ManyToManyField', 'OneToOneField', 'JSONField',
'SlugField', 'PositiveIntegerField', 'BigIntegerField',
'SmallIntegerField', 'UUIDField', 'TimeField',
'GenericIPAddressField', 'BinaryField', 'DurationField',
# 支持通用字段类型
'string', 'text', 'integer', 'float', 'decimal', 'boolean',
'date', 'datetime', 'email', 'url', 'image', 'file', 'json',
'slug', 'uuid', 'time', 'ip', 'binary', 'duration'
}
# 通用字段类型到Django字段类型的映射
self.field_type_mapping = {
'string': 'CharField',
'text': 'TextField',
'integer': 'IntegerField',
'float': 'FloatField',
'decimal': 'DecimalField',
'boolean': 'BooleanField',
'date': 'DateField',
'datetime': 'DateTimeField',
'email': 'EmailField',
'url': 'URLField',
'image': 'ImageField',
'file': 'FileField',
'json': 'JSONField',
'slug': 'SlugField',
'uuid': 'UUIDField',
'time': 'TimeField',
'ip': 'GenericIPAddressField',
'binary': 'BinaryField',
'duration': 'DurationField'
}
self.supported_operations = {
'create', 'read', 'update', 'delete', 'list', 'search', 'filter', 'get', 'retrieve'
}
def parse_yaml_file(self, yaml_file_path: str) -> Dict[str, Any]:
"""
解析YAML配置文件
Args:
yaml_file_path: YAML文件路径
Returns:
Dict[str, Any]: 解析后的配置字典
Raises:
FileNotFoundError: 当文件不存在时
yaml.YAMLError: 当YAML格式错误时
ValueError: 当配置验证失败时
"""
if not os.path.exists(yaml_file_path):
raise FileNotFoundError(f"YAML配置文件不存在: {yaml_file_path}")
try:
with open(yaml_file_path, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
except yaml.YAMLError as e:
raise yaml.YAMLError(f"YAML文件格式错误: {e}")
# 验证配置结构
validated_config = self._validate_config(config)
return validated_config
def _validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
验证YAML配置结构
Args:
config: 原始配置字典
Returns:
Dict[str, Any]: 验证后的配置字典
Raises:
ValueError: 当配置验证失败时
"""
if not isinstance(config, dict):
raise ValueError("YAML配置必须是字典格式")
# 验证必需的顶级字段
required_fields = ['app_name', 'models']
for field in required_fields:
if field not in config:
raise ValueError(f"缺少必需字段: {field}")
# 验证应用名称
app_name = config['app_name']
if not isinstance(app_name, str) or not app_name.strip():
raise ValueError("app_name必须是非空字符串")
# 验证模型配置
models = config['models']
if isinstance(models, dict):
# 如果models是字典格式转换为列表格式
models_list = []
for model_name, model_config in models.items():
model_config['name'] = model_name
models_list.append(model_config)
models = models_list
elif not isinstance(models, list) or len(models) == 0:
raise ValueError("models必须是非空列表或字典")
validated_models = []
for i, model in enumerate(models):
validated_model = self._validate_model_config(model, i)
validated_models.append(validated_model)
# 构建验证后的配置
validated_config = {
'app_name': app_name.strip(),
'models': validated_models,
'output_dir': config.get('output_dir', './generated_code'),
'version': config.get('version', '1.0.0'),
'description': config.get('description', ''),
'author': config.get('author', ''),
'global_settings': config.get('global_settings', {})
}
return validated_config
def _validate_model_config(self, model: Dict[str, Any], index: int) -> Dict[str, Any]:
"""
验证单个模型配置
Args:
model: 模型配置字典
index: 模型在列表中的索引
Returns:
Dict[str, Any]: 验证后的模型配置
Raises:
ValueError: 当模型配置验证失败时
"""
if not isinstance(model, dict):
raise ValueError(f"模型配置[{index}]必须是字典格式")
# 验证必需字段
if 'name' not in model:
raise ValueError(f"模型配置[{index}]缺少必需字段: name")
if 'fields' not in model:
raise ValueError(f"模型配置[{index}]缺少必需字段: fields")
model_name = model['name']
if not isinstance(model_name, str) or not model_name.strip():
raise ValueError(f"模型配置[{index}]的name必须是非空字符串")
# 验证字段配置
fields = model['fields']
if not isinstance(fields, list) or len(fields) == 0:
raise ValueError(f"模型{model_name}的fields必须是非空列表")
validated_fields = []
for j, field in enumerate(fields):
validated_field = self._validate_field_config(field, model_name, j)
validated_fields.append(validated_field)
# 验证操作配置
operations = model.get('operations', ['create', 'read', 'update', 'delete', 'list'])
if not isinstance(operations, list):
raise ValueError(f"模型{model_name}的operations必须是列表")
for op in operations:
if op not in self.supported_operations:
raise ValueError(f"模型{model_name}包含不支持的操作: {op}")
# 构建验证后的模型配置
validated_model = {
'name': model_name.strip(),
'fields': validated_fields,
'operations': operations,
'table_name': model.get('table_name'),
'verbose_name': model.get('verbose_name', model_name),
'verbose_name_plural': model.get('verbose_name_plural'),
'ordering': model.get('ordering', []),
'permissions': model.get('permissions', []),
'validators': model.get('validators', {}),
'meta_options': model.get('meta_options', {}),
'admin_config': model.get('admin_config', {}),
'api_config': model.get('api_config', {})
}
return validated_model
def _validate_field_config(self, field: Dict[str, Any], model_name: str, index: int) -> Dict[str, Any]:
"""
验证单个字段配置
Args:
field: 字段配置字典
model_name: 所属模型名称
index: 字段在列表中的索引
Returns:
Dict[str, Any]: 验证后的字段配置
Raises:
ValueError: 当字段配置验证失败时
"""
if not isinstance(field, dict):
raise ValueError(f"模型{model_name}的字段配置[{index}]必须是字典格式")
# 验证必需字段
if 'name' not in field:
raise ValueError(f"模型{model_name}的字段配置[{index}]缺少必需字段: name")
if 'type' not in field:
raise ValueError(f"模型{model_name}的字段配置[{index}]缺少必需字段: type")
field_name = field['name']
field_type = field['type']
if not isinstance(field_name, str) or not field_name.strip():
raise ValueError(f"模型{model_name}的字段配置[{index}]的name必须是非空字符串")
# 检查字段类型是否支持包括通用类型和Django类型
if field_type not in self.supported_field_types and field_type not in self.field_type_mapping:
raise ValueError(f"模型{model_name}的字段{field_name}使用了不支持的类型: {field_type}")
# 如果是通用类型转换为Django字段类型
django_field_type = self.field_type_mapping.get(field_type, field_type)
# 移除调试输出
print(f"字段类型转换: {field_type} -> {django_field_type}")
# 构建验证后的字段配置
validated_field = {
'name': field['name'],
'type': django_field_type, # 使用转换后的Django字段类型
'verbose_name': field.get('verbose_name', field['name']),
'help_text': field.get('help_text', f"{field['name']}字段"),
'required': field.get('required', False),
'null': not field.get('required', False),
'blank': not field.get('required', False)
}
# 添加其他字段属性
for key in ['max_length', 'default', 'unique', 'choices', 'max_digits', 'decimal_places', 'upload_to']:
if key in field:
validated_field[key] = field[key]
# 移除None值
validated_field = {k: v for k, v in validated_field.items() if v is not None}
return validated_field
def generate_yaml_template(self, output_path: str = 'app_template.yaml') -> str:
"""
生成YAML配置文件模板
Args:
output_path: 输出文件路径
Returns:
str: 生成的模板内容
"""
template = {
'app_name': 'hertz_studio_django_example',
'version': '1.0.0',
'description': 'Django应用示例',
'author': 'Hertz Studio',
'output_dir': './generated_code',
'global_settings': {
'use_uuid_primary_key': False,
'add_created_updated_fields': True,
'add_status_field': True,
'default_permissions': ['add', 'change', 'delete', 'view']
},
'models': [
{
'name': 'User',
'verbose_name': '用户',
'verbose_name_plural': '用户列表',
'table_name': 'example_user',
'ordering': ['-created_at'],
'fields': [
{
'name': 'username',
'type': 'CharField',
'max_length': 150,
'unique': True,
'verbose_name': '用户名',
'help_text': '用户登录名'
},
{
'name': 'email',
'type': 'EmailField',
'unique': True,
'verbose_name': '邮箱',
'help_text': '用户邮箱地址'
},
{
'name': 'phone',
'type': 'CharField',
'max_length': 20,
'blank': True,
'null': True,
'verbose_name': '手机号',
'help_text': '用户手机号码'
},
{
'name': 'avatar',
'type': 'ImageField',
'upload_to': 'avatars/',
'blank': True,
'null': True,
'verbose_name': '头像',
'help_text': '用户头像图片'
},
{
'name': 'is_active',
'type': 'BooleanField',
'default': True,
'verbose_name': '是否激活',
'help_text': '用户账户是否激活'
}
],
'operations': ['create', 'read', 'update', 'delete', 'list', 'search'],
'permissions': ['add_user', 'change_user', 'delete_user', 'view_user'],
'validators': {
'username': 'validate_username',
'email': 'validate_email'
},
'admin_config': {
'list_display': ['username', 'email', 'is_active', 'created_at'],
'list_filter': ['is_active', 'created_at'],
'search_fields': ['username', 'email']
},
'api_config': {
'pagination': True,
'filters': ['is_active'],
'search_fields': ['username', 'email'],
'ordering_fields': ['username', 'created_at']
}
}
]
}
with open(output_path, 'w', encoding='utf-8') as file:
yaml.dump(template, file, default_flow_style=False, allow_unicode=True, indent=2)
return yaml.dump(template, default_flow_style=False, allow_unicode=True, indent=2)

View File

@@ -0,0 +1,24 @@
departments = [
{
'dept_name': 'Hertz科技',
'dept_code': 'hertz_tech',
'leader': '超级管理员',
'email': 'admin@hertz.com',
'sort_order': 1,
'parent_id': None
},
{
'dept_name': '技术部',
'dept_code': 'tech_dept',
'leader': '技术总监',
'sort_order': 1,
'parent_code': 'hertz_tech'
},
{
'dept_name': '运营部',
'dept_code': 'ops_dept',
'leader': '运营总监',
'sort_order': 2,
'parent_code': 'hertz_tech'
}
]

View File

@@ -0,0 +1,625 @@
#!/usr/bin/env python
"""
菜单配置文件
包含系统所有菜单配置和动态菜单生成功能
"""
from typing import List, Dict, Any, Optional
def add_new_menus(new_menus: List[Dict[str, Any]]) -> None:
"""
动态添加新菜单到配置中
Args:
new_menus: 新菜单配置列表
"""
global menus
menus.extend(new_menus)
print(f"已添加 {len(new_menus)} 个新菜单到配置中")
def get_menus_by_parent(parent_code: Optional[str] = None) -> List[Dict[str, Any]]:
"""
根据父级代码获取菜单列表
Args:
parent_code: 父级菜单代码None表示获取顶级菜单
Returns:
List[Dict]: 菜单列表
"""
return [menu for menu in menus if menu.get('parent_code') == parent_code]
def get_menu_by_code(menu_code: str) -> Optional[Dict[str, Any]]:
"""
根据菜单代码获取菜单配置
Args:
menu_code: 菜单代码
Returns:
Optional[Dict]: 菜单配置未找到返回None
"""
for menu in menus:
if menu.get('menu_code') == menu_code:
return menu
return None
def generate_menu_permissions() -> List[str]:
"""
生成所有菜单权限列表
Returns:
List[str]: 权限代码列表
"""
permissions = []
for menu in menus:
if menu.get('permission'):
permissions.append(menu['permission'])
return permissions
menus = [
# 系统管理目录
{
'menu_name': '系统管理',
'menu_code': 'system',
'menu_type': 1, # 目录
'path': '/system',
'icon': 'system',
'sort_order': 1,
'parent_code': None
},
# 用户管理菜单
{
'menu_name': '用户管理',
'menu_code': 'system:user',
'menu_type': 2, # 菜单
'path': '/system/user',
'component': 'system/user/index',
'icon': 'user',
'permission': 'system:user:list',
'sort_order': 1,
'parent_code': 'system'
},
{
'menu_name': '用户查询',
'menu_code': 'system:user:query',
'menu_type': 3, # 按钮
'permission': 'system:user:query',
'sort_order': 1,
'parent_code': 'system:user'
},
{
'menu_name': '用户新增',
'menu_code': 'system:user:add',
'menu_type': 3,
'permission': 'system:user:add',
'sort_order': 2,
'parent_code': 'system:user'
},
{
'menu_name': '用户修改',
'menu_code': 'system:user:edit',
'menu_type': 3,
'permission': 'system:user:edit',
'sort_order': 3,
'parent_code': 'system:user'
},
{
'menu_name': '用户删除',
'menu_code': 'system:user:remove',
'menu_type': 3,
'permission': 'system:user:remove',
'sort_order': 4,
'parent_code': 'system:user'
},
{
'menu_name': '分配角色',
'menu_code': 'system:user:role',
'menu_type': 3,
'permission': 'system:user:role',
'sort_order': 5,
'parent_code': 'system:user'
},
# 角色管理菜单
{
'menu_name': '角色管理',
'menu_code': 'system:role',
'menu_type': 2,
'path': '/system/role',
'component': 'system/role/index',
'icon': 'role',
'permission': 'system:role:list',
'sort_order': 2,
'parent_code': 'system'
},
{
'menu_name': '角色查询',
'menu_code': 'system:role:query',
'menu_type': 3,
'permission': 'system:role:query',
'sort_order': 1,
'parent_code': 'system:role'
},
{
'menu_name': '角色新增',
'menu_code': 'system:role:add',
'menu_type': 3,
'permission': 'system:role:add',
'sort_order': 2,
'parent_code': 'system:role'
},
{
'menu_name': '角色修改',
'menu_code': 'system:role:edit',
'menu_type': 3,
'permission': 'system:role:edit',
'sort_order': 3,
'parent_code': 'system:role'
},
{
'menu_name': '角色删除',
'menu_code': 'system:role:remove',
'menu_type': 3,
'permission': 'system:role:remove',
'sort_order': 4,
'parent_code': 'system:role'
},
{
'menu_name': '分配权限',
'menu_code': 'system:role:menu',
'menu_type': 3,
'permission': 'system:role:menu',
'sort_order': 5,
'parent_code': 'system:role'
},
# 菜单管理菜单
{
'menu_name': '菜单管理',
'menu_code': 'system:menu',
'menu_type': 2,
'path': '/system/menu',
'component': 'system/menu/index',
'icon': 'menu',
'permission': 'system:menu:list',
'sort_order': 3,
'parent_code': 'system'
},
{
'menu_name': '菜单查询',
'menu_code': 'system:menu:query',
'menu_type': 3,
'permission': 'system:menu:query',
'sort_order': 1,
'parent_code': 'system:menu'
},
{
'menu_name': '菜单新增',
'menu_code': 'system:menu:add',
'menu_type': 3,
'permission': 'system:menu:add',
'sort_order': 2,
'parent_code': 'system:menu'
},
{
'menu_name': '菜单修改',
'menu_code': 'system:menu:edit',
'menu_type': 3,
'permission': 'system:menu:edit',
'sort_order': 3,
'parent_code': 'system:menu'
},
{
'menu_name': '菜单删除',
'menu_code': 'system:menu:remove',
'menu_type': 3,
'permission': 'system:menu:remove',
'sort_order': 4,
'parent_code': 'system:menu'
},
# 部门管理菜单
{
'menu_name': '部门管理',
'menu_code': 'system:dept',
'menu_type': 2,
'path': '/system/dept',
'component': 'system/dept/index',
'icon': 'dept',
'permission': 'system:dept:list',
'sort_order': 4,
'parent_code': 'system'
},
{
'menu_name': '部门查询',
'menu_code': 'system:dept:query',
'menu_type': 3,
'permission': 'system:dept:query',
'sort_order': 1,
'parent_code': 'system:dept'
},
{
'menu_name': '部门新增',
'menu_code': 'system:dept:add',
'menu_type': 3,
'permission': 'system:dept:add',
'sort_order': 2,
'parent_code': 'system:dept'
},
{
'menu_name': '部门修改',
'menu_code': 'system:dept:edit',
'menu_type': 3,
'permission': 'system:dept:edit',
'sort_order': 3,
'parent_code': 'system:dept'
},
{
'menu_name': '部门删除',
'menu_code': 'system:dept:remove',
'menu_type': 3,
'permission': 'system:dept:remove',
'sort_order': 4,
'parent_code': 'system:dept'
},
# ==================== 工作室模块 ====================
# 工作室目录
{
'menu_name': '工作室',
'menu_code': 'studio',
'menu_type': 1, # 目录
'path': '/studio',
'icon': 'appstore',
'sort_order': 10,
'parent_code': None
},
# ==================== 通知公告模块 ====================
# 通知公告菜单
{
'menu_name': '通知公告',
'menu_code': 'studio:notice',
'menu_type': 2, # 菜单
'path': '/studio/notice',
'component': 'studio/notice/index',
'icon': 'notice',
'permission': 'studio:notice:list',
'sort_order': 1,
'parent_code': 'studio'
},
{
'menu_name': '通知查询',
'menu_code': 'studio:notice:query',
'menu_type': 3, # 按钮
'permission': 'studio:notice:query',
'sort_order': 1,
'parent_code': 'studio:notice'
},
{
'menu_name': '通知新增',
'menu_code': 'studio:notice:add',
'menu_type': 3,
'permission': 'studio:notice:add',
'sort_order': 2,
'parent_code': 'studio:notice'
},
{
'menu_name': '通知修改',
'menu_code': 'studio:notice:edit',
'menu_type': 3,
'permission': 'studio:notice:edit',
'sort_order': 3,
'parent_code': 'studio:notice'
},
{
'menu_name': '通知删除',
'menu_code': 'studio:notice:remove',
'menu_type': 3,
'permission': 'studio:notice:remove',
'sort_order': 4,
'parent_code': 'studio:notice'
},
# ==================== AI对话模块 ====================
# AI对话菜单
{
'menu_name': 'AI对话',
'menu_code': 'studio:ai',
'menu_type': 2, # 菜单
'path': '/studio/ai',
'component': 'studio/ai/index',
'icon': 'robot',
'permission': 'studio:ai:list',
'sort_order': 2,
'parent_code': 'studio'
},
{
'menu_name': 'AI查询',
'menu_code': 'studio:ai:query',
'menu_type': 3, # 按钮
'permission': 'studio:ai:query',
'sort_order': 1,
'parent_code': 'studio:ai'
},
{
'menu_name': 'AI新增',
'menu_code': 'studio:ai:add',
'menu_type': 3,
'permission': 'studio:ai:add',
'sort_order': 2,
'parent_code': 'studio:ai'
},
{
'menu_name': 'AI修改',
'menu_code': 'studio:ai:edit',
'menu_type': 3,
'permission': 'studio:ai:edit',
'sort_order': 3,
'parent_code': 'studio:ai'
},
{
'menu_name': 'AI删除',
'menu_code': 'studio:ai:remove',
'menu_type': 3,
'permission': 'studio:ai:remove',
'sort_order': 4,
'parent_code': 'studio:ai'
},
# ==================== 系统监控模块 ====================
# 系统监控菜单
{
'menu_name': '系统监控',
'menu_code': 'studio:system_monitor',
'menu_type': 2, # 菜单
'path': '/studio/monitor',
'component': 'studio/system_monitor/index',
'icon': 'monitor',
'permission': 'studio:system_monitor:list',
'sort_order': 3,
'parent_code': 'studio'
},
{
'menu_name': '监控查询',
'menu_code': 'studio:system_monitor:query',
'menu_type': 3, # 按钮
'permission': 'studio:system_monitor:query',
'sort_order': 1,
'parent_code': 'studio:system_monitor'
},
{
'menu_name': '监控新增',
'menu_code': 'studio:system_monitor:add',
'menu_type': 3,
'permission': 'studio:system_monitor:add',
'sort_order': 2,
'parent_code': 'studio:system_monitor'
},
{
'menu_name': '监控修改',
'menu_code': 'studio:system_monitor:edit',
'menu_type': 3,
'permission': 'studio:system_monitor:edit',
'sort_order': 3,
'parent_code': 'studio:system_monitor'
},
{
'menu_name': '监控删除',
'menu_code': 'studio:system_monitor:remove',
'menu_type': 3,
'permission': 'studio:system_monitor:remove',
'sort_order': 4,
'parent_code': 'studio:system_monitor'
},
# 知识管理菜单
{
'menu_name': '知识管理',
'menu_code': 'system:wiki',
'menu_type': 2, # 菜单
'path': '/system/wiki',
'component': 'system/wiki/index',
'icon': 'book',
'permission': 'system:wiki:list',
'sort_order': 6,
'parent_code': 'system'
},
# 知识分类管理
{
'menu_name': '知识分类',
'menu_code': 'system:wiki:category',
'menu_type': 2, # 菜单
'path': '/system/wiki/category',
'component': 'system/wiki/category/index',
'icon': 'folder',
'permission': 'system:wiki:category:list',
'sort_order': 1,
'parent_code': 'system:wiki'
},
{
'menu_name': '分类查询',
'menu_code': 'system:wiki:category:query',
'menu_type': 3, # 按钮
'permission': 'system:wiki:category:query',
'sort_order': 1,
'parent_code': 'system:wiki:category'
},
{
'menu_name': '分类新增',
'menu_code': 'system:wiki:category:add',
'menu_type': 3,
'permission': 'system:wiki:category:add',
'sort_order': 2,
'parent_code': 'system:wiki:category'
},
{
'menu_name': '分类修改',
'menu_code': 'system:wiki:category:edit',
'menu_type': 3,
'permission': 'system:wiki:category:update',
'sort_order': 3,
'parent_code': 'system:wiki:category'
},
{
'menu_name': '分类删除',
'menu_code': 'system:wiki:category:remove',
'menu_type': 3,
'permission': 'system:wiki:category:remove',
'sort_order': 4,
'parent_code': 'system:wiki:category'
},
# 知识文章管理
{
'menu_name': '知识文章',
'menu_code': 'system:wiki:article',
'menu_type': 2, # 菜单
'path': '/system/wiki/article',
'component': 'system/wiki/article/index',
'icon': 'file-text',
'permission': 'system:wiki:article:list',
'sort_order': 2,
'parent_code': 'system:wiki'
},
{
'menu_name': '文章查询',
'menu_code': 'system:wiki:article:query',
'menu_type': 3, # 按钮
'permission': 'system:wiki:article:query',
'sort_order': 1,
'parent_code': 'system:wiki:article'
},
{
'menu_name': '文章新增',
'menu_code': 'system:wiki:article:add',
'menu_type': 3,
'permission': 'system:wiki:article:add',
'sort_order': 2,
'parent_code': 'system:wiki:article'
},
{
'menu_name': '文章修改',
'menu_code': 'system:wiki:article:edit',
'menu_type': 3,
'permission': 'system:wiki:article:edit',
'sort_order': 3,
'parent_code': 'system:wiki:article'
},
{
'menu_name': '文章删除',
'menu_code': 'system:wiki:article:remove',
'menu_type': 3,
'permission': 'system:wiki:article:remove',
'sort_order': 4,
'parent_code': 'system:wiki:article'
},
{
'menu_name': '文章发布',
'menu_code': 'system:wiki:article:publish',
'menu_type': 3,
'permission': 'system:wiki:article:publish',
'sort_order': 5,
'parent_code': 'system:wiki:article'
},
# 日志管理菜单
{
'menu_name': '日志管理',
'menu_code': 'system:log',
'menu_type': 2, # 菜单
'path': '/system/log',
'component': 'system/log/index',
'icon': 'log',
'permission': 'log.view_operationlog',
'sort_order': 7,
'parent_code': 'system'
},
{
'menu_name': '日志查询',
'menu_code': 'system:log:query',
'menu_type': 3, # 按钮
'permission': 'log.view_operationlog',
'sort_order': 1,
'parent_code': 'system:log'
},
{
'menu_name': '日志详情',
'menu_code': 'system:log:detail',
'menu_type': 3,
'permission': 'log.view_operationlog',
'sort_order': 2,
'parent_code': 'system:log'
},
# YOLO古建筑识别模块
{
'menu_name': 'YOLO识别',
'menu_code': 'studio:yolo',
'menu_type': 2, # 菜单
'path': '/studio/yolo',
'component': 'studio/yolo/index',
'icon': 'camera',
'permission': 'studio:yolo:list',
'sort_order': 4,
'parent_code': 'studio'
},
{
'menu_name': '图像识别',
'menu_code': 'studio:yolo:recognition',
'menu_type': 3, # 按钮
'permission': 'studio:yolo:recognition',
'sort_order': 1,
'parent_code': 'studio:yolo'
},
{
'menu_name': '识别历史',
'menu_code': 'studio:yolo:history',
'menu_type': 3,
'permission': 'studio:yolo:history',
'sort_order': 2,
'parent_code': 'studio:yolo'
},
{
'menu_name': '问答记录',
'menu_code': 'studio:yolo:question',
'menu_type': 3,
'permission': 'studio:yolo:question',
'sort_order': 3,
'parent_code': 'studio:yolo'
},
{
'menu_name': '记录收藏',
'menu_code': 'studio:yolo:favorite',
'menu_type': 3,
'permission': 'studio:yolo:favorite',
'sort_order': 4,
'parent_code': 'studio:yolo'
},
{
'menu_name': '记录删除',
'menu_code': 'studio:yolo:delete',
'menu_type': 3,
'permission': 'studio:yolo:delete',
'sort_order': 5,
'parent_code': 'studio:yolo'
},
{
'menu_name': '统计信息',
'menu_code': 'studio:yolo:statistics',
'menu_type': 3,
'permission': 'studio:yolo:statistics',
'sort_order': 6,
'parent_code': 'studio:yolo'
}
]

View File

@@ -0,0 +1,20 @@
roles = [
{
'role_name': '超级管理员',
'role_code': 'super_admin',
'description': '系统超级管理员,拥有所有权限',
'sort_order': 1
},
{
'role_name': '系统管理员',
'role_code': 'system_admin',
'description': '系统管理员,拥有系统管理权限',
'sort_order': 2
},
{
'role_name': '普通用户',
'role_code': 'normal_user',
'description': '普通用户,基础权限',
'sort_order': 3
}
]

View File

@@ -0,0 +1,4 @@
from .encryption_utils import EncryptionUtils
from .password_hashers import MD5PasswordHasher
__all__ = ['EncryptionUtils', 'MD5PasswordHasher']

View File

@@ -0,0 +1,160 @@
import base64
import hashlib
import secrets
from typing import Optional
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
class EncryptionUtils:
"""
加密工具类
提供各种加密解密功能
"""
@staticmethod
def generate_salt(length: int = 32) -> str:
"""
生成随机盐值
Args:
length: 盐值长度
Returns:
str: Base64编码的盐值
"""
salt = secrets.token_bytes(length)
return base64.b64encode(salt).decode('utf-8')
@staticmethod
def md5_hash(data: str, salt: str = '') -> str:
"""
MD5哈希加密
Args:
data: 待加密数据
salt: 盐值
Returns:
str: MD5哈希值
"""
combined = data + salt
md5_hash = hashlib.md5(combined.encode('utf-8'))
return md5_hash.hexdigest()
@staticmethod
def sha256_hash(data: str, salt: str = '') -> str:
"""
SHA256哈希加密
Args:
data: 待加密数据
salt: 盐值
Returns:
str: SHA256哈希值
"""
combined = data + salt
sha256_hash = hashlib.sha256(combined.encode('utf-8'))
return sha256_hash.hexdigest()
@staticmethod
def generate_key_from_password(password: str, salt: bytes) -> bytes:
"""
从密码生成加密密钥
Args:
password: 密码
salt: 盐值
Returns:
bytes: 加密密钥
"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
return key
@staticmethod
def encrypt_data(data: str, password: str) -> Optional[str]:
"""
使用密码加密数据
Args:
data: 待加密数据
password: 加密密码
Returns:
Optional[str]: 加密后的数据Base64编码失败返回None
"""
try:
# 生成随机盐值
salt = secrets.token_bytes(16)
# 从密码生成密钥
key = EncryptionUtils.generate_key_from_password(password, salt)
# 创建Fernet实例
fernet = Fernet(key)
# 加密数据
encrypted_data = fernet.encrypt(data.encode('utf-8'))
# 将盐值和加密数据组合
combined = salt + encrypted_data
# 返回Base64编码的结果
return base64.b64encode(combined).decode('utf-8')
except Exception:
return None
@staticmethod
def decrypt_data(encrypted_data: str, password: str) -> Optional[str]:
"""
使用密码解密数据
Args:
encrypted_data: 加密后的数据Base64编码
password: 解密密码
Returns:
Optional[str]: 解密后的数据失败返回None
"""
try:
# Base64解码
combined = base64.b64decode(encrypted_data.encode('utf-8'))
# 分离盐值和加密数据
salt = combined[:16]
encrypted_bytes = combined[16:]
# 从密码生成密钥
key = EncryptionUtils.generate_key_from_password(password, salt)
# 创建Fernet实例
fernet = Fernet(key)
# 解密数据
decrypted_data = fernet.decrypt(encrypted_bytes)
return decrypted_data.decode('utf-8')
except Exception:
return None
@staticmethod
def generate_random_key() -> str:
"""
生成随机加密密钥
Returns:
str: Base64编码的随机密钥
"""
key = Fernet.generate_key()
return key.decode('utf-8')

View File

@@ -0,0 +1,79 @@
import hashlib
from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import constant_time_compare
class MD5PasswordHasher(BasePasswordHasher):
"""
MD5密码哈希器
用于兼容旧系统的MD5密码加密
"""
algorithm = 'md5'
library = 'hashlib'
def encode(self, password, salt):
"""
编码密码
Args:
password: 原始密码
salt: 盐值
Returns:
str: 编码后的密码
"""
hash_obj = hashlib.md5((salt + password).encode('utf-8'))
hash_value = hash_obj.hexdigest()
return f'{self.algorithm}${salt}${hash_value}'
def verify(self, password, encoded):
"""
验证密码
Args:
password: 原始密码
encoded: 编码后的密码
Returns:
bool: 验证结果
"""
algorithm, salt, hash_value = encoded.split('$', 2)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, salt)
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
"""
返回密码的安全摘要信息
Args:
encoded: 编码后的密码
Returns:
dict: 摘要信息
"""
algorithm, salt, hash_value = encoded.split('$', 2)
assert algorithm == self.algorithm
return {
'algorithm': algorithm,
'salt': salt[:6] + '...',
'hash': hash_value[:6] + '...',
}
def harden_runtime(self, password, encoded):
"""
硬化运行时间MD5不需要
"""
pass
def must_update(self, encoded):
"""
检查是否需要更新密码编码
Args:
encoded: 编码后的密码
Returns:
bool: 是否需要更新
"""
return False

View File

@@ -0,0 +1,3 @@
from .email_service import EmailService
__all__ = ['EmailService']

View File

@@ -0,0 +1,174 @@
from django.core.mail import EmailMultiAlternatives
from django.conf import settings
from django.utils.html import strip_tags
from typing import Optional, Dict, Any
import logging
logger = logging.getLogger(__name__)
class EmailService:
"""
邮件发送服务类
提供统一的邮件发送接口
"""
@staticmethod
def send_email(
recipient_email: str,
subject: str,
html_content: str,
text_content: Optional[str] = None,
from_email: Optional[str] = None
) -> bool:
"""
发送邮件
Args:
recipient_email: 收件人邮箱
subject: 邮件主题
html_content: HTML内容
text_content: 纯文本内容(可选)
from_email: 发件人邮箱(可选)
Returns:
bool: 发送是否成功
"""
try:
# 检查邮件配置
if not settings.EMAIL_HOST_USER or not settings.EMAIL_HOST_PASSWORD:
logger.warning("邮件配置不完整,使用控制台输出模式")
logger.info(f"收件人: {recipient_email}")
logger.info(f"主题: {subject}")
logger.info(f"内容: {text_content or strip_tags(html_content)[:200]}...")
return True
# 如果没有提供纯文本内容从HTML中提取
if not text_content:
text_content = strip_tags(html_content)
# 创建邮件
email = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=from_email or settings.DEFAULT_FROM_EMAIL,
to=[recipient_email]
)
# 添加HTML内容
email.attach_alternative(html_content, "text/html")
# 发送邮件
email.send()
logger.info(f"邮件发送成功: {recipient_email}")
return True
except Exception as e:
logger.error(f"邮件发送失败: {str(e)}")
return False
@staticmethod
def send_verification_code(
recipient_email: str,
recipient_name: str,
verification_code: str,
code_type: str = 'register'
) -> bool:
"""
发送验证码邮件
Args:
recipient_email: 收件人邮箱
recipient_name: 收件人姓名
verification_code: 验证码
code_type: 验证码类型register/reset_password
Returns:
bool: 发送是否成功
"""
# 根据类型生成邮件内容
email_content = EmailService._generate_verification_email_content(
recipient_name, verification_code, code_type
)
return EmailService.send_email(
recipient_email=recipient_email,
subject=email_content['subject'],
html_content=email_content['html_content'],
text_content=email_content['text_content']
)
@staticmethod
def _generate_verification_email_content(
recipient_name: str,
verification_code: str,
code_type: str
) -> Dict[str, str]:
"""
生成验证码邮件内容
Args:
recipient_name: 收件人姓名
verification_code: 验证码
code_type: 验证码类型
Returns:
Dict[str, str]: 包含subject, html_content, text_content的字典
"""
if code_type == 'register':
subject = '🔐 注册验证码 - Hertz Server Django'
title = '注册验证码'
description = '感谢您注册 Hertz Server Django请使用以下验证码完成注册'
elif code_type == 'reset_password':
subject = '🔐 密码重置验证码 - Hertz Server Django'
title = '密码重置验证码'
description = '您正在重置密码,请使用以下验证码完成操作:'
else:
subject = '🔐 邮箱验证码 - Hertz Server Django'
title = '邮箱验证码'
description = '请使用以下验证码完成验证:'
html_content = f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #28a745; color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">🔐 {title}</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>{description}</p>
<div style="text-align: center; margin: 40px 0;">
<div style="background: #f8f9fa; border: 2px dashed #28a745; padding: 20px; border-radius: 10px; display: inline-block;">
<span style="font-size: 32px; font-weight: bold; color: #28a745; letter-spacing: 5px;">{verification_code}</span>
</div>
</div>
<p style="color: #666; font-size: 14px;">验证码有效期为5分钟请尽快使用。如果您没有进行此操作请忽略此邮件。</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
text_content = f'''
{title}
亲爱的 {recipient_name}
{description}
验证码:{verification_code}
验证码有效期为5分钟请尽快使用。如果您没有进行此操作请忽略此邮件。
此致
Hertz Server Django 团队
'''
return {
'subject': subject,
'html_content': html_content,
'text_content': text_content
}

View File

@@ -0,0 +1,368 @@
from functools import wraps
from django.contrib.auth.models import User
from django.http import JsonResponse
import json
import logging
# 设置日志记录器
logger = logging.getLogger(__name__)
def operation_log(action_type, module, description=None, target_model=None):
"""
操作日志装饰器
Args:
action_type (str): 操作类型,如 'create', 'update', 'delete'
module (str): 操作模块,如 '用户管理', '通知管理'
description (str, optional): 操作描述,如果不提供则自动生成
target_model (str, optional): 目标模型名称
Usage:
@operation_log('create', '用户管理', '创建新用户', 'User')
def create_user(request):
# 视图函数逻辑
pass
"""
def decorator(view_func):
@wraps(view_func)
def wrapper(*args, **kwargs):
# 检查是否是类视图方法第一个参数是self第二个是request
if len(args) >= 2 and hasattr(args[0], '__class__') and hasattr(args[1], 'META'):
# 类视图方法
self_instance = args[0]
request = args[1]
print(f"\n=== 类视图装饰器被调用 ===")
print(f"类名: {self_instance.__class__.__name__}")
print(f"函数名: {view_func.__name__}")
elif len(args) >= 1 and hasattr(args[0], 'META'):
# 函数视图
request = args[0]
print(f"\n=== 函数视图装饰器被调用 ===")
print(f"函数名: {view_func.__name__}")
else:
# 无法识别的调用方式,直接执行原函数
return view_func(*args, **kwargs)
print(f"操作类型: {action_type}")
print(f"模块: {module}")
print(f"请求方法: {request.method}")
print(f"请求路径: {request.path}")
print(f"=== 装饰器调用信息结束 ===\n")
# 延迟导入避免循环导入
from hertz_studio_django_log.models import OperationLog
# 获取用户信息
user = None
if hasattr(request, 'user') and request.user.is_authenticated:
user = request.user
# 获取请求信息
ip_address = get_client_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '')[:500] # 限制长度
# 获取请求数据
request_data = get_request_data(request)
response = None
response_status = 200
target_id = None
try:
# 执行原始视图函数
if len(args) >= 2 and hasattr(args[0], '__class__') and hasattr(args[1], 'META'):
# 类视图方法去掉self参数
response = view_func(args[0], request, *args[2:], **kwargs)
else:
# 函数视图
response = view_func(request, *args[1:], **kwargs)
# 获取响应状态码
if hasattr(response, 'status_code'):
response_status = response.status_code
# 尝试获取目标ID
target_id = extract_target_id(response, kwargs)
except Exception as e:
response_status = 500
logger.error(f"视图函数执行错误: {str(e)}")
# 打印详细的错误信息到控制台
import traceback
print(f"\n=== 装饰器捕获到异常 ===")
print(f"异常类型: {type(e).__name__}")
print(f"异常信息: {str(e)}")
print(f"异常堆栈:")
traceback.print_exc()
print(f"=== 装饰器异常信息结束 ===\n")
# 重新抛出异常让Django处理
raise
# 异步记录操作日志
try:
print(f"\n=== 开始记录操作日志 ===")
print(f"用户: {user}")
print(f"操作类型: {action_type}")
print(f"模块: {module}")
print(f"描述: {description or f'{action_type}操作 - {module}'}")
print(f"目标模型: {target_model}")
print(f"目标ID: {target_id}")
print(f"IP地址: {ip_address}")
print(f"请求数据: {request_data}")
print(f"响应状态: {response_status}")
log_operation(
user=user,
action_type=action_type,
module=module,
description=description or f"{action_type}操作 - {module}",
target_model=target_model,
target_id=target_id,
ip_address=ip_address,
user_agent=user_agent,
request_data=request_data,
response_status=response_status
)
print(f"操作日志记录成功")
print(f"=== 操作日志记录结束 ===\n")
except Exception as log_error:
# 日志记录失败不应该影响正常业务
print(f"\n=== 操作日志记录失败 ===")
print(f"错误类型: {type(log_error).__name__}")
print(f"错误信息: {str(log_error)}")
import traceback
traceback.print_exc()
print(f"=== 操作日志记录失败信息结束 ===\n")
logger.error(f"操作日志记录失败: {log_error}")
return response
return wrapper
return decorator
def get_client_ip(request):
"""
获取客户端真实IP地址
"""
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 get_request_data(request):
"""
安全地获取请求数据
"""
request_data = {}
try:
if request.method == 'GET':
request_data = dict(request.GET)
elif request.method == 'POST':
content_type = request.META.get('CONTENT_TYPE', '')
if 'application/json' in content_type:
if hasattr(request, 'body') and request.body:
request_data = json.loads(request.body.decode('utf-8'))
else:
request_data = dict(request.POST)
elif request.method in ['PUT', 'PATCH', 'DELETE']:
if hasattr(request, 'body') and request.body:
request_data = json.loads(request.body.decode('utf-8'))
except (json.JSONDecodeError, UnicodeDecodeError, AttributeError) as e:
logger.warning(f"解析请求数据失败: {str(e)}")
request_data = {}
# 过滤敏感信息
sensitive_fields = ['password', 'token', 'secret', 'key', 'csrf_token']
if isinstance(request_data, dict):
for field in sensitive_fields:
if field in request_data:
request_data[field] = '***'
return request_data
def extract_target_id(response, kwargs):
"""
从响应或URL参数中提取目标ID
"""
target_id = None
# 从响应中获取ID
try:
if hasattr(response, 'data') and isinstance(response.data, dict):
if 'id' in response.data:
target_id = response.data['id']
elif 'data' in response.data and isinstance(response.data['data'], dict):
if 'id' in response.data['data']:
target_id = response.data['data']['id']
except (AttributeError, TypeError):
pass
# 从URL参数中获取ID
if not target_id and kwargs:
for key, value in kwargs.items():
if key.endswith('_id') or key == 'pk' or key == 'id':
try:
target_id = int(value)
break
except (ValueError, TypeError):
pass
return target_id
def log_operation(user, action_type, module, description, target_model=None,
target_id=None, ip_address=None, user_agent=None,
request_data=None, response_status=None):
"""
记录操作日志
"""
from hertz_studio_django_log.models import OperationLog
# 如果用户未登录,记录为匿名用户操作(可选择是否记录)
if user is None:
# 对于某些重要操作,即使是匿名用户也要记录
# 可以通过配置决定是否记录匿名用户操作
logger.info(f"记录匿名用户的操作日志: {action_type} - {module} - {description}")
# 为匿名用户创建一个临时用户对象或跳过用户字段
# 这里我们选择跳过匿名用户的日志记录,保持原有逻辑
# 如果需要记录匿名用户操作可以注释掉下面的return语句
return
# 限制数据长度避免数据库错误
if user_agent and len(user_agent) > 500:
user_agent = user_agent[:500]
if description and len(description) > 255:
description = description[:255]
# 限制请求数据大小
if request_data and len(str(request_data)) > 5000:
request_data = {'message': '请求数据过大,已省略'}
# 使用模型的create_log方法来创建日志
OperationLog.create_log(
user=user,
action_type=action_type,
module=module,
description=description,
target_model=target_model,
target_id=target_id,
ip_address=ip_address,
user_agent=user_agent,
request_data=request_data,
response_status=response_status
)
def auto_log(action_type, module=None):
"""
自动日志装饰器,根据视图类名和方法名自动推断模块和描述
Args:
action_type (str): 操作类型
module (str, optional): 模块名称,如果不提供则从视图类名推断
"""
def decorator(view_func):
@wraps(view_func)
def wrapper(*args, **kwargs):
# 延迟导入避免循环导入
from hertz_studio_django_log.models import OperationLog
# 获取request对象
request = None
if args and hasattr(args[0], '__class__') and hasattr(args[0], '__module__'):
# 这是类方法request是第二个参数
request = args[1] if len(args) > 1 else None
view_instance = args[0]
class_name = view_instance.__class__.__name__
else:
# 这是函数视图request是第一个参数
request = args[0] if args else None
class_name = view_func.__name__
# 检查request对象是否有效
if not request or not hasattr(request, 'META'):
return view_func(*args, **kwargs)
# 获取用户信息
user = None
if hasattr(request, 'user') and request.user.is_authenticated:
user = request.user
# 自动推断模块名称
auto_module = module
if not auto_module:
if 'User' in class_name:
auto_module = '用户管理'
elif 'Notification' in class_name:
auto_module = '通知管理'
elif 'Config' in class_name:
auto_module = '系统配置'
elif 'File' in class_name:
auto_module = '文件管理'
elif 'AI' in class_name or 'Chat' in class_name:
auto_module = 'AI助手'
elif 'Wiki' in class_name:
auto_module = '知识管理'
else:
auto_module = '系统管理'
# 自动生成描述
action_map = {
'create': '创建',
'update': '更新',
'delete': '删除',
'view': '查看',
'list': '列表查看',
'login': '登录',
'logout': '登出'
}
auto_description = f"{action_map.get(action_type, action_type)} - {auto_module}"
# 获取请求信息
ip_address = get_client_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '')[:500]
request_data = get_request_data(request)
response = None
response_status = 200
target_id = None
try:
# 执行原始视图函数
response = view_func(*args, **kwargs)
# 获取响应状态码
if hasattr(response, 'status_code'):
response_status = response.status_code
# 尝试获取目标ID
target_id = extract_target_id(response, kwargs)
except Exception as e:
response_status = 500
logger.error(f"视图函数执行错误: {str(e)}")
# 重新抛出异常让Django处理
raise
# 异步记录操作日志
try:
log_operation(
user=user,
action_type=action_type,
module=auto_module,
description=auto_description,
target_model=auto_module,
target_id=target_id,
ip_address=ip_address,
user_agent=user_agent,
request_data=request_data,
response_status=response_status
)
except Exception as log_error:
# 日志记录失败不应该影响正常业务
logger.error(f"操作日志记录失败: {log_error}")
return response
return wrapper
return decorator

View File

@@ -0,0 +1,207 @@
import json
import asyncio
import aiohttp
import requests
from typing import List, Dict, Any, AsyncGenerator, Optional
from django.conf import settings
class OllamaClient:
"""
Ollama API客户端
用于与Ollama服务通信获取LLM响应
"""
def __init__(self, base_url: str = None):
"""
初始化Ollama客户端
Args:
base_url: Ollama服务地址默认从settings中获取
"""
self.base_url = base_url or getattr(settings, "OLLAMA_BASE_URL", "http://localhost:11434")
self.generate_url = f"{self.base_url}/api/generate"
self.chat_url = f"{self.base_url}/api/chat"
async def generate_stream(
self,
model: str,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
top_p: float = 0.9,
top_k: int = 40,
max_tokens: int = 2048
) -> AsyncGenerator[str, None]:
"""
生成文本流
Args:
model: 模型名称如deepseek-r1:1.5b
prompt: 用户输入的提示
system_prompt: 系统提示
temperature: 温度参数
top_p: top-p采样参数
top_k: top-k采样参数
max_tokens: 最大生成token数
Yields:
生成的文本片段
"""
payload = {
"model": model,
"prompt": prompt,
"stream": True,
"options": {
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"num_predict": max_tokens
}
}
if system_prompt:
payload["system"] = system_prompt
async with aiohttp.ClientSession() as session:
async with session.post(self.generate_url, json=payload) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(f"Ollama API error: {response.status} - {error_text}")
# 流式读取响应
async for line in response.content:
if not line:
continue
try:
data = json.loads(line)
if "response" in data:
yield data["response"]
# 如果接收到完成标志,退出
if data.get("done", False):
break
except json.JSONDecodeError:
continue
async def chat_stream(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float = 0.7,
top_p: float = 0.9,
top_k: int = 40,
max_tokens: int = 2048
) -> AsyncGenerator[str, None]:
"""
流式聊天接口
Args:
model: 模型名称
messages: 消息历史,格式为[{"role": "user", "content": "..."}, ...]
temperature: 温度参数
top_p: top-p采样参数
top_k: top-k采样参数
max_tokens: 最大生成token数
Yields:
生成的文本片段
"""
payload = {
"model": model,
"messages": messages,
"stream": True,
"options": {
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"num_predict": max_tokens
}
}
async with aiohttp.ClientSession() as session:
async with session.post(self.chat_url, json=payload) as response:
if response.status != 200:
error_text = await response.text()
raise Exception(f"Ollama API error: {response.status} - {error_text}")
# 流式读取响应
async for line in response.content:
if not line:
continue
try:
data = json.loads(line)
if "message" in data and "content" in data["message"]:
yield data["message"]["content"]
# 如果接收到完成标志,退出
if data.get("done", False):
break
except json.JSONDecodeError:
continue
async def check_model_availability(self, model: str) -> bool:
"""
检查模型是否可用
Args:
model: 模型名称
Returns:
模型是否可用
"""
async with aiohttp.ClientSession() as session:
async with session.get(f"{self.base_url}/api/tags") as response:
if response.status != 200:
return False
data = await response.json()
models = data.get("models", [])
return any(m["name"] == model for m in models)
def chat_completion(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float = 0.7,
top_p: float = 0.9,
top_k: int = 40,
max_tokens: int = 2048
) -> str:
"""
同步聊天接口用于REST API
Args:
model: 模型名称
messages: 消息历史,格式为[{"role": "user", "content": "..."}, ...]
temperature: 温度参数
top_p: top-p采样参数
top_k: top-k采样参数
max_tokens: 最大生成token数
Returns:
生成的完整回复文本
"""
payload = {
"model": model,
"messages": messages,
"stream": False, # 非流式
"options": {
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"num_predict": max_tokens
}
}
response = requests.post(self.chat_url, json=payload)
if response.status_code != 200:
raise Exception(f"Ollama API error: {response.status_code} - {response.text}")
data = response.json()
if "message" in data and "content" in data["message"]:
return data["message"]["content"]
raise Exception("Unexpected response format from Ollama API")

View File

@@ -0,0 +1,185 @@
from django.http import JsonResponse
from typing import Any, Dict, Optional
class HertzResponse:
"""
Hertz统一响应类
提供标准化的API响应格式
"""
@staticmethod
def success(data: Any = None, message: str = "操作成功", code: int = 200) -> JsonResponse:
"""
成功响应
Args:
data: 响应数据
message: 响应消息
code: HTTP状态码
Returns:
JsonResponse: 标准化的成功响应
"""
response_data = {
'success': True,
'code': code,
'message': message,
'data': data
}
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def fail(message: str = "操作失败", data: Any = None, code: int = 400) -> JsonResponse:
"""
失败响应(业务逻辑失败)
Args:
message: 失败消息
data: 响应数据
code: HTTP状态码
Returns:
JsonResponse: 标准化的失败响应
"""
response_data = {
'success': False,
'code': code,
'message': message,
'data': data
}
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def error(message: str = "系统错误", error: str = None, code: int = 500) -> JsonResponse:
"""
错误响应(系统错误)
Args:
message: 错误消息
error: 详细错误信息
code: HTTP状态码
Returns:
JsonResponse: 标准化的错误响应
"""
response_data = {
'success': False,
'code': code,
'message': message
}
if error:
response_data['error'] = error
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def unauthorized(message: str = "未授权访问", code: int = 401) -> JsonResponse:
"""
未授权响应
Args:
message: 响应消息
code: HTTP状态码
Returns:
JsonResponse: 标准化的未授权响应
"""
response_data = {
'success': False,
'code': code,
'message': message
}
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def forbidden(message: str = "禁止访问", code: int = 403) -> JsonResponse:
"""
禁止访问响应
Args:
message: 响应消息
code: HTTP状态码
Returns:
JsonResponse: 标准化的禁止访问响应
"""
response_data = {
'success': False,
'code': code,
'message': message
}
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def not_found(message: str = "资源未找到", code: int = 404) -> JsonResponse:
"""
资源未找到响应
Args:
message: 响应消息
code: HTTP状态码
Returns:
JsonResponse: 标准化的资源未找到响应
"""
response_data = {
'success': False,
'code': code,
'message': message
}
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def validation_error(message: str = "参数验证失败", errors: Dict = None, code: int = 422) -> JsonResponse:
"""
参数验证错误响应
Args:
message: 响应消息
errors: 验证错误详情
code: HTTP状态码
Returns:
JsonResponse: 标准化的参数验证错误响应
"""
response_data = {
'success': False,
'code': code,
'message': message
}
if errors:
response_data['errors'] = errors
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})
@staticmethod
def custom(success: bool, message: str, data: Any = None, code: int = 200, **kwargs) -> JsonResponse:
"""
自定义响应
Args:
success: 是否成功
message: 响应消息
data: 响应数据
code: HTTP状态码
**kwargs: 其他自定义字段
Returns:
JsonResponse: 自定义响应
"""
response_data = {
'success': success,
'code': code,
'message': message
}
if data is not None:
response_data['data'] = data
# 添加其他自定义字段
response_data.update(kwargs)
return JsonResponse(response_data, status=code, json_dumps_params={'ensure_ascii': False})

View File

@@ -0,0 +1,3 @@
from .HertzResponse import HertzResponse
__all__ = ['HertzResponse']

View File

@@ -0,0 +1,5 @@
from .email_validator import EmailValidator
from .phone_validator import PhoneValidator
from .password_validator import PasswordValidator
__all__ = ['EmailValidator', 'PhoneValidator', 'PasswordValidator']

View File

@@ -0,0 +1,117 @@
import re
from typing import Tuple
class EmailValidator:
"""
邮箱验证器
提供邮箱格式验证功能
"""
# 邮箱正则表达式
EMAIL_PATTERN = re.compile(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
)
@staticmethod
def is_valid_email(email: str) -> bool:
"""
验证邮箱格式是否正确
Args:
email: 邮箱地址
Returns:
bool: 邮箱格式是否正确
"""
if not email or not isinstance(email, str):
return False
return bool(EmailValidator.EMAIL_PATTERN.match(email.strip()))
@staticmethod
def validate_email(email: str) -> Tuple[bool, str]:
"""
验证邮箱并返回详细信息
Args:
email: 邮箱地址
Returns:
Tuple[bool, str]: (是否有效, 提示信息)
"""
if not email:
return False, "邮箱地址不能为空"
if not isinstance(email, str):
return False, "邮箱地址必须是字符串"
email = email.strip()
if len(email) == 0:
return False, "邮箱地址不能为空"
if len(email) > 254:
return False, "邮箱地址长度不能超过254个字符"
if not EmailValidator.EMAIL_PATTERN.match(email):
return False, "邮箱地址格式不正确"
# 检查本地部分长度(@符号前的部分)
local_part = email.split('@')[0]
if len(local_part) > 64:
return False, "邮箱用户名部分长度不能超过64个字符"
return True, "邮箱地址格式正确"
@staticmethod
def normalize_email(email: str) -> str:
"""
标准化邮箱地址
Args:
email: 邮箱地址
Returns:
str: 标准化后的邮箱地址
"""
if not email or not isinstance(email, str):
return ''
# 去除首尾空格并转换为小写
return email.strip().lower()
@staticmethod
def get_email_domain(email: str) -> str:
"""
获取邮箱域名
Args:
email: 邮箱地址
Returns:
str: 邮箱域名
"""
if not EmailValidator.is_valid_email(email):
return ''
return email.split('@')[1].lower()
@staticmethod
def is_common_email_provider(email: str) -> bool:
"""
检查是否为常见邮箱服务商
Args:
email: 邮箱地址
Returns:
bool: 是否为常见邮箱服务商
"""
common_providers = {
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
'163.com', '126.com', 'qq.com', 'sina.com', 'sohu.com'
}
domain = EmailValidator.get_email_domain(email)
return domain in common_providers

View File

@@ -0,0 +1,229 @@
import re
from typing import Tuple, List
class PasswordValidator:
"""
密码验证器
提供密码强度验证功能
"""
"""
生产环境
"""
# @staticmethod
# def validate_password_strength(password: str, min_length: int = 8, max_length: int = 128) -> Tuple[bool, List[str]]:
# """
# 验证密码强度
#
# Args:
# password: 密码
# min_length: 最小长度
# max_length: 最大长度
#
# Returns:
# Tuple[bool, List[str]]: (是否通过验证, 错误信息列表)
# """
# errors = []
#
# if not password:
# errors.append("密码不能为空")
# return False, errors
#
# if not isinstance(password, str):
# errors.append("密码必须是字符串")
# return False, errors
#
# # 检查长度
# if len(password) < min_length:
# errors.append(f"密码长度至少{min_length}位")
#
# if len(password) > max_length:
# errors.append(f"密码长度不能超过{max_length}位")
#
# # 检查是否包含数字
# if not re.search(r'\d', password):
# errors.append("密码必须包含至少一个数字")
#
# # 检查是否包含小写字母
# if not re.search(r'[a-z]', password):
# errors.append("密码必须包含至少一个小写字母")
#
# # 检查是否包含大写字母
# if not re.search(r'[A-Z]', password):
# errors.append("密码必须包含至少一个大写字母")
#
# return len(errors) == 0, errors
"""
开发环境
"""
# 默认使用下面开发环境,在生产环境中请使用上面的校验规则!
@staticmethod
def validate_password_strength(password: str,
min_length: int = 6,
max_length: int = 128) -> Tuple[bool, List[str]]:
"""
验证密码强度(仅检查长度,不少于 6 位即可)
Args:
password: 密码
min_length: 最小长度(默认 6
max_length: 最大长度(默认 128
Returns:
Tuple[bool, List[str]]: (是否通过验证, 错误信息列表)
"""
errors = []
if not password:
errors.append("密码不能为空")
return False, errors
if not isinstance(password, str):
errors.append("密码必须是字符串")
return False, errors
# 仅长度检查
if len(password) < min_length:
errors.append(f"密码长度至少{min_length}")
if len(password) > max_length:
errors.append(f"密码长度不能超过{max_length}")
return len(errors) == 0, errors
@staticmethod
def validate_simple_password(password: str, min_length: int = 6, max_length: int = 128) -> Tuple[bool, str]:
"""
简单密码验证(只检查长度和基本字符)
Args:
password: 密码
min_length: 最小长度
max_length: 最大长度
Returns:
Tuple[bool, str]: (是否通过验证, 错误信息)
"""
if not password:
return False, "密码不能为空"
if not isinstance(password, str):
return False, "密码必须是字符串"
if len(password) < min_length:
return False, f"密码长度至少{min_length}"
if len(password) > max_length:
return False, f"密码长度不能超过{max_length}"
# 检查是否包含数字或字母
if not re.search(r'[a-zA-Z0-9]', password):
return False, "密码必须包含字母或数字"
return True, "密码格式正确"
@staticmethod
def check_common_passwords(password: str) -> bool:
"""
检查是否为常见弱密码
Args:
password: 密码
Returns:
bool: 是否为常见弱密码
"""
common_passwords = {
'123456', 'password', '123456789', '12345678', '12345',
'1234567', '1234567890', 'qwerty', 'abc123', '111111',
'123123123', 'admin', 'letmein', 'welcome', 'monkey',
'1234', 'dragon', 'pass', 'master', 'hello',
'freedom', 'whatever', 'qazwsx', 'trustno1', 'jordan23'
}
return password.lower() in common_passwords
@staticmethod
def calculate_password_score(password: str) -> int:
"""
计算密码强度分数0-100
Args:
password: 密码
Returns:
int: 密码强度分数
"""
if not password:
return 0
score = 0
# 长度分数最多30分
length_score = min(len(password) * 2, 30)
score += length_score
# 字符类型分数
if re.search(r'[a-z]', password): # 小写字母
score += 10
if re.search(r'[A-Z]', password): # 大写字母
score += 10
if re.search(r'\d', password): # 数字
score += 10
if re.search(r'[!@#$%^&*(),.?":{}|<>]', password): # 特殊字符
score += 15
# 字符多样性分数
unique_chars = len(set(password))
diversity_score = min(unique_chars * 2, 25)
score += diversity_score
# 扣分项
if PasswordValidator.check_common_passwords(password):
score -= 30
# 重复字符扣分
if re.search(r'(.)\1{2,}', password): # 连续3个相同字符
score -= 10
# 连续数字或字母扣分
if re.search(r'(012|123|234|345|456|567|678|789|890)', password):
score -= 5
if re.search(r'(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)', password.lower()):
score -= 5
return max(0, min(100, score))
@staticmethod
def get_password_strength_level(password: str) -> str:
"""
获取密码强度等级
Args:
password: 密码
Returns:
str: 密码强度等级
"""
score = PasswordValidator.calculate_password_score(password)
if score >= 80:
return "很强"
elif score >= 60:
return ""
elif score >= 40:
return "中等"
elif score >= 20:
return ""
else:
return "很弱"

View File

@@ -0,0 +1,178 @@
import re
from typing import Tuple
class PhoneValidator:
"""
手机号验证器
提供手机号格式验证功能
"""
# 中国大陆手机号正则表达式
CHINA_MOBILE_PATTERN = re.compile(
r'^1[3-9]\d{9}$'
)
# 国际手机号正则表达式(简化版)
INTERNATIONAL_PATTERN = re.compile(
r'^\+?[1-9]\d{1,14}$'
)
@staticmethod
def is_valid_china_mobile(phone: str) -> bool:
"""
验证中国大陆手机号格式是否正确
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
if not phone or not isinstance(phone, str):
return False
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
return bool(PhoneValidator.CHINA_MOBILE_PATTERN.match(phone))
@staticmethod
def is_valid_international_phone(phone: str) -> bool:
"""
验证国际手机号格式是否正确
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
if not phone or not isinstance(phone, str):
return False
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
return bool(PhoneValidator.INTERNATIONAL_PATTERN.match(phone))
@staticmethod
def is_valid_phone(phone: str) -> bool:
"""
验证手机号格式是否正确(默认使用中国大陆手机号验证)
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
return PhoneValidator.is_valid_china_mobile(phone)
@staticmethod
def validate_china_mobile(phone: str) -> Tuple[bool, str]:
"""
验证中国大陆手机号并返回详细信息
Args:
phone: 手机号
Returns:
Tuple[bool, str]: (是否有效, 提示信息)
"""
if not phone:
return False, "手机号不能为空"
if not isinstance(phone, str):
return False, "手机号必须是字符串"
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
if len(phone) == 0:
return False, "手机号不能为空"
if len(phone) != 11:
return False, "手机号长度必须为11位"
if not phone.isdigit():
return False, "手机号只能包含数字"
if not phone.startswith('1'):
return False, "手机号必须以1开头"
if phone[1] not in '3456789':
return False, "手机号第二位必须是3-9之间的数字"
return True, "手机号格式正确"
@staticmethod
def normalize_phone(phone: str) -> str:
"""
标准化手机号
Args:
phone: 手机号
Returns:
str: 标准化后的手机号
"""
if not phone or not isinstance(phone, str):
return ''
# 去除空格、连字符和括号
phone = phone.replace(' ', '').replace('-', '').replace('(', '').replace(')', '')
# 如果是中国大陆手机号且以+86开头去除+86
if phone.startswith('+86') and len(phone) == 14:
phone = phone[3:]
elif phone.startswith('86') and len(phone) == 13:
phone = phone[2:]
return phone
@staticmethod
def get_mobile_carrier(phone: str) -> str:
"""
获取手机号运营商(仅支持中国大陆)
Args:
phone: 手机号
Returns:
str: 运营商名称
"""
if not PhoneValidator.is_valid_china_mobile(phone):
return '未知'
phone = PhoneValidator.normalize_phone(phone)
prefix = phone[:3]
# 中国移动
china_mobile_prefixes = {
'134', '135', '136', '137', '138', '139',
'147', '150', '151', '152', '157', '158', '159',
'172', '178', '182', '183', '184', '187', '188',
'195', '197', '198'
}
# 中国联通
china_unicom_prefixes = {
'130', '131', '132', '145', '155', '156',
'166', '171', '175', '176', '185', '186', '196'
}
# 中国电信
china_telecom_prefixes = {
'133', '149', '153', '173', '174', '177',
'180', '181', '189', '191', '193', '199'
}
if prefix in china_mobile_prefixes:
return '中国移动'
elif prefix in china_unicom_prefixes:
return '中国联通'
elif prefix in china_telecom_prefixes:
return '中国电信'
else:
return '未知运营商'

View File

@@ -0,0 +1,244 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
视频转换工具类
用于将视频转换为H.264编码的MP4格式确保浏览器兼容性
"""
import os
import subprocess
import json
import logging
from pathlib import Path
from typing import Optional, Dict, Any
logger = logging.getLogger(__name__)
class VideoConverter:
"""视频格式转换工具类"""
def __init__(self):
"""初始化视频转换器"""
self.ffmpeg_available = self._check_ffmpeg()
def _check_ffmpeg(self) -> bool:
"""
检查FFmpeg是否可用
Returns:
bool: FFmpeg是否可用
"""
try:
result = subprocess.run(
['ffmpeg', '-version'],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
logger.warning("FFmpeg未安装或不可用")
return False
def get_video_info(self, video_path: str) -> Optional[Dict[str, Any]]:
"""
获取视频信息
Args:
video_path: 视频文件路径
Returns:
Dict: 视频信息字典,包含编码格式、分辨率、时长等
"""
if not self.ffmpeg_available:
logger.error("FFmpeg不可用无法获取视频信息")
return None
try:
cmd = [
'ffprobe', '-v', 'quiet', '-print_format', 'json',
'-show_format', '-show_streams', video_path
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
logger.error(f"获取视频信息失败: {result.stderr}")
return None
info = json.loads(result.stdout)
# 查找视频流
video_stream = None
for stream in info.get('streams', []):
if stream.get('codec_type') == 'video':
video_stream = stream
break
if not video_stream:
logger.error("未找到视频流")
return None
return {
'codec': video_stream.get('codec_name', 'unknown'),
'width': video_stream.get('width', 0),
'height': video_stream.get('height', 0),
'duration': float(info.get('format', {}).get('duration', 0)),
'size': os.path.getsize(video_path) if os.path.exists(video_path) else 0
}
except Exception as e:
logger.error(f"获取视频信息时出错: {str(e)}")
return None
def is_h264_compatible(self, video_path: str) -> bool:
"""
检查视频是否已经是H.264编码
Args:
video_path: 视频文件路径
Returns:
bool: 是否为H.264编码
"""
video_info = self.get_video_info(video_path)
if not video_info:
return False
return video_info.get('codec', '').lower() == 'h264'
def convert_to_h264(self, input_path: str, output_path: Optional[str] = None,
quality: str = 'medium', overwrite: bool = True) -> Optional[str]:
"""
将视频转换为H.264编码的MP4格式
Args:
input_path: 输入视频文件路径
output_path: 输出视频文件路径(可选)
quality: 质量设置 ('high', 'medium', 'low')
overwrite: 是否覆盖已存在的文件
Returns:
str: 转换后的文件路径失败返回None
"""
if not self.ffmpeg_available:
logger.error("FFmpeg不可用无法进行视频转换")
return None
input_path = Path(input_path)
if not input_path.exists():
logger.error(f"输入文件不存在: {input_path}")
return None
# 检查是否已经是H.264格式
if self.is_h264_compatible(str(input_path)):
logger.info(f"视频已经是H.264格式: {input_path}")
return str(input_path)
# 生成输出文件路径
if output_path is None:
output_path = input_path.parent / f"{input_path.stem}_h264.mp4"
else:
output_path = Path(output_path)
# 检查输出文件是否已存在
if output_path.exists() and not overwrite:
logger.info(f"输出文件已存在: {output_path}")
return str(output_path)
# 设置质量参数
quality_settings = {
'high': {'crf': '18', 'preset': 'slow'},
'medium': {'crf': '23', 'preset': 'medium'},
'low': {'crf': '28', 'preset': 'fast'}
}
settings = quality_settings.get(quality, quality_settings['medium'])
# 构建FFmpeg命令
cmd = [
'ffmpeg',
'-i', str(input_path),
'-c:v', 'libx264', # 使用H.264编码器
'-crf', settings['crf'], # 质量设置
'-preset', settings['preset'], # 编码速度预设
'-c:a', 'aac', # 音频编码器
'-b:a', '128k', # 音频比特率
'-movflags', '+faststart', # 优化网络播放
'-y' if overwrite else '-n', # 覆盖或跳过已存在文件
str(output_path)
]
try:
logger.info(f"开始转换视频: {input_path} -> {output_path}")
# 执行转换
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
if output_path.exists():
logger.info(f"视频转换成功: {output_path}")
# 验证转换结果
if self.is_h264_compatible(str(output_path)):
return str(output_path)
else:
logger.error("转换完成但格式验证失败")
return None
else:
logger.error("转换完成但输出文件未生成")
return None
else:
logger.error(f"视频转换失败: {result.stderr}")
return None
except subprocess.TimeoutExpired:
logger.error("视频转换超时")
return None
except Exception as e:
logger.error(f"视频转换过程中出错: {str(e)}")
return None
def ensure_h264_format(self, video_path: str, quality: str = 'medium') -> str:
"""
确保视频为H.264格式,如果不是则自动转换
Args:
video_path: 视频文件路径
quality: 转换质量设置
Returns:
str: H.264格式的视频文件路径
"""
if self.is_h264_compatible(video_path):
return video_path
converted_path = self.convert_to_h264(video_path, quality=quality)
return converted_path if converted_path else video_path
def get_conversion_status(self) -> Dict[str, Any]:
"""
获取转换器状态信息
Returns:
Dict: 状态信息
"""
return {
'ffmpeg_available': self.ffmpeg_available,
'supported_formats': ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv'] if self.ffmpeg_available else [],
'output_format': 'H.264 MP4'
}
# 创建全局实例
video_converter = VideoConverter()