代码上传

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

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*.c
*.pyc
*.pyd
dist
build
.pypirc

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 yang kunhao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14
MANIFEST.in Normal file
View File

@@ -0,0 +1,14 @@
# 保留安装和说明文件
include README.md
include LICENSE
include requirements.txt
# 包含二进制文件
recursive-include hertz_studio_django_xxx *.py
recursive-include hertz_studio_django_xxx/migrations *.py
# 排除源代码和缓存但已通过include保留了必要的.py文件
global-exclude *.pyc
global-exclude __pycache__

0
README.md Normal file
View File

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()

60
license_verifier.py Normal file
View File

@@ -0,0 +1,60 @@
import platform
import uuid
import hashlib
import requests
# 常量配置
ACTIVATE_URL = "http://activate.hzsystems.cn/api/activate_machine"
PACKAGE_NAME = "hertz_studio_django_captcha"
def get_machine_id() -> str:
"""生成机器码。
根据当前系统信息平台、架构、MAC地址生成唯一机器码
使用 SHA256 取前16位并转大写前缀为 HERTZ_STUDIO_。
"""
system_info = f"{platform.platform()}-{platform.machine()}-{uuid.getnode()}"
return 'HERTZ_STUDIO_' + hashlib.sha256(system_info.encode()).hexdigest()[:16].upper()
def request_verify_machine_code(package_name: str, machine_code: str):
"""请求后端校验机器码。
向授权服务器提交包名和机器码,返回 JSON 响应;
如果网络异常,返回 None。
"""
try:
resp = requests.post(
ACTIVATE_URL,
json={"package_name": package_name, "machine_code": machine_code},
timeout=10,
)
return resp.json()
except requests.RequestException as e:
print(f"机器码验证请求失败: {e}")
return None
def verify_machine_license() -> None:
"""运行时验证授权。
在 Django 应用启动时执行授权验证:打印提示、生成机器码并请求校验;
校验失败则抛出 RuntimeError 阻止应用启动。
"""
machine_id = get_machine_id()
print(f"您的机器码: {machine_id}, 当前包名: {PACKAGE_NAME}")
print("请将此机器码发送给作者进行注册。")
resp = request_verify_machine_code(PACKAGE_NAME, machine_id)
if resp and resp.get("success") is True:
print("=" * 60)
print("机器码验证成功!")
print("=" * 60)
return
print("=" * 60)
print("机器码验证失败!请联系作者获取运行权限。")
print("=" * 60)
raise RuntimeError("Hertz Xxxxx license verification failed")

0
requirements.txt Normal file
View File

107
setup.py Normal file
View File

@@ -0,0 +1,107 @@
from setuptools import setup, find_packages
import sys
import platform
import uuid
import hashlib
import requests
package_name = 'hertz_studio_django_xxx'
# 读取README文件内容
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
# 读取requirements文件
def read_requirements():
with open("requirements.txt", "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip() and not line.startswith("#")]
# 机器码验证请求
def request_verify_machine_code(package_name, machine_code):
"""请求验证机器码"""
url = "http://activate.hzsystems.cn/api/activate_machine"
data = {"package_name": package_name, "machine_code": machine_code}
try:
response = requests.post(url, json=data, timeout=10)
return response.json()
except requests.RequestException as e:
print(f"机器码验证请求失败: {e}")
return None
# 机器码验证功能
def verify_machine_license():
"""验证机器码"""
print("\n" + "="*60)
print("欢迎使用 Hertz System xxx!")
print("="*60)
print("本软件需要机器码验证才能安装。")
print("请联系作者获取安装权限hertz studio(563161210@qq.com)")
print("="*60)
# 获取系统信息
system_info = f"{platform.platform()}-{platform.machine()}-{uuid.getnode()}"
machine_id = 'HERTZ_STUDIO_'+hashlib.sha256(system_info.encode()).hexdigest()[:16].upper()
print(f"您的机器码: {machine_id},当前安装的包名: {package_name}")
print("请将此机器码发送给作者进行注册。")
# 请求验证机器码
response = request_verify_machine_code(package_name, machine_id)
if response.get('success') == True:
print("=" * 60)
print("机器码验证成功!")
print("=" * 60)
else:
print("=" * 60)
print("机器码验证失败!请联系作者获取安装权限。")
print("=" * 60)
sys.exit(1)
# 在安装前验证机器码
if 'install' in sys.argv or 'bdist_wheel' in sys.argv or 'sdist' in sys.argv:
verify_machine_license()
setup(
name=package_name, # PyPI上的包名
version="1.0.1", # 版本号
author="yang kunhao", # 作者名
author_email="563161210@qq.com", # 作者邮箱
description="一个功能强大的Django验证码应用", # 简短描述
long_description=long_description,
long_description_content_type="text/markdown",
url="http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx", # 项目地址
packages=find_packages(include=[f"{package_name}*"]), # 自动发现并同时打包两个包
include_package_data=True, # 包含MANIFEST.in中定义的文件
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Framework :: Django",
"Framework :: Django :: 3.0",
"Framework :: Django :: 3.1",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries :: Python Modules",
],
license="MIT", # 使用 SPDX 许可证标识符
python_requires=">=3.10", # Python版本要求
install_requires=read_requirements(), # 依赖包
keywords="django xxx verification security", # 关键词
project_urls={
"Bug Reports": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx/issues",
"Source": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx",
"Documentation": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx#readme",
},
)

114
使用手册.md Normal file
View File

@@ -0,0 +1,114 @@
# APP打包步骤
## 一、复制APP
将要打包的APP复制粘贴到根目录
## 二、编辑APP下面的apps.py文件
在apps.py文件中添加该函数
```python
def ready(self):
"""Django 应用启动时的钩子。
在此执行运行时授权校验,未授权则抛出异常阻止应用启动。
"""
try:
from .license_verifier import verify_machine_license
verify_machine_license()
except Exception:
# 直接再抛出,让 Django 启动失败并展示错误信息
raise
```
## 三、迁移license
1. 将根目录下面的license_verifier.py复制粘贴到要打包的APP下
2. 修改license_verifier.py文件
将下面的PACKAGE_NAME的值改为要把包的app名称
```
PACKAGE_NAME = "hertz_studio_django_xxxxx"
```
修改该脚本最后一行代码将Xxxxx改为打包的app简称:
```
raise RuntimeError("Hertz Xxxxx license verification failed")
```
## 四、修改MANIFEST.in文件
将下面的hertz_studio_django_xxx改为打包的app名称
```
# 包含二进制文件
recursive-include hertz_studio_django_xxx *.py
recursive-include hertz_studio_django_xxx/migrations *.py
```
## 五、修改根目录下面的setup.py文件
- 修改成要打包的app名称将hertz_studio_django_xxx改为打包的app名称
```
package_name = 'hertz_studio_django_xxx'
```
- 将XXX改为app简称
```
print("欢迎使用 Hertz System xxx!")
```
- 更改下载的项目地址将hertz_studio_django_xxx改为打包的app名称
```
url="http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx", # 项目地址
```
- 更改关键词将XXX改为app简称
```
keywords="django XXXX verification security", # 关键词
```
- 修改project_urls将xxx改为app简称
```
project_urls={
"Bug Reports": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx/issues",
"Source": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx",
"Documentation": "http://hzgit.hzsystems.cn/hertz_studio_django/hertz_studio_django_xxx#readme",
}
```
## 六、打包
完成上述的修改后,在终端的项目输入以下命令并回车进行打包
```
python setup.py sdist
```
运行完成后会在dist目录下生成一个打包过后的文件