From 3a7b41ebce59cde0598525314a7bbc56c0fd75a0 Mon Sep 17 00:00:00 2001
From: pony <1356137040@qq.com>
Date: Mon, 17 Nov 2025 18:22:56 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=B8=8A=E4=BC=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 6 +
LICENSE | 21 +
MANIFEST.in | 14 +
README.md | 0
hertz_studio_django_utils/README.md | 438 +++++++++++
hertz_studio_django_utils/__init__.py | 1 +
.../code_generator/__init__.py | 39 +
.../code_generator/app_generator.py | 286 ++++++++
.../code_generator/base_generator.py | 262 +++++++
.../copy/hertz_server_django/README.md | 401 ++++++++++
.../copy/hertz_server_django/__init__.py | 0
.../copy/hertz_server_django/asgi.py | 35 +
.../copy/hertz_server_django/settings.py | 335 +++++++++
.../copy/hertz_server_django/urls.py | 62 ++
.../copy/hertz_server_django/views.py | 13 +
.../copy/hertz_server_django/wsgi.py | 16 +
.../code_generator/django_code_generator.py | 687 ++++++++++++++++++
.../code_generator/menu_generator.py | 434 +++++++++++
.../code_generator/model_generator.py | 262 +++++++
.../code_generator/serializer_generator.py | 336 +++++++++
.../code_generator/template_engine.py | 248 +++++++
.../.mako_modules/django/models.mako.py | 225 ++++++
.../.mako_modules/django/serializers.mako.py | 173 +++++
.../.mako_modules/django/urls.mako.py | 129 ++++
.../.mako_modules/django/views.mako.py | 328 +++++++++
.../code_generator/templates/django/apps.mako | 23 +
.../templates/django/models.mako | 173 +++++
.../templates/django/project_urls.mako | 62 ++
.../templates/django/serializers.mako | 153 ++++
.../templates/django/settings.mako | 345 +++++++++
.../code_generator/templates/django/urls.mako | 56 ++
.../templates/django/views.mako | 329 +++++++++
.../code_generator/test/apps.py | 6 +
.../code_generator/test/models.py | 30 +
.../code_generator/test/serializers.py | 24 +
.../code_generator/test/urls.py | 6 +
.../code_generator/test/views.py | 0
.../code_generator/url_generator.py | 350 +++++++++
.../code_generator/view_generator.py | 416 +++++++++++
.../code_generator/yaml_generator_cli.py | 184 +++++
.../code_generator/yaml_parser.py | 372 ++++++++++
hertz_studio_django_utils/config/__init__.py | 0
.../config/departments_config.py | 24 +
.../config/menus_config.py | 625 ++++++++++++++++
.../config/roles_config.py | 20 +
hertz_studio_django_utils/crypto/__init__.py | 4 +
.../crypto/encryption_utils.py | 160 ++++
.../crypto/password_hashers.py | 79 ++
hertz_studio_django_utils/email/__init__.py | 3 +
.../email/email_service.py | 174 +++++
hertz_studio_django_utils/log/__init__.py | 0
.../log/log_decorator.py | 368 ++++++++++
hertz_studio_django_utils/ollama/__init__.py | 0
.../ollama/ollama_client.py | 207 ++++++
.../responses/HertzResponse.py | 185 +++++
.../responses/__init__.py | 3 +
.../validators/__init__.py | 5 +
.../validators/email_validator.py | 117 +++
.../validators/password_validator.py | 229 ++++++
.../validators/phone_validator.py | 178 +++++
hertz_studio_django_utils/yolo/__init__.py | 0
.../yolo/video_converter.py | 244 +++++++
license_verifier.py | 60 ++
requirements.txt | 0
setup.py | 107 +++
使用手册.md | 114 +++
66 files changed, 10186 insertions(+)
create mode 100644 .gitignore
create mode 100644 LICENSE
create mode 100644 MANIFEST.in
create mode 100644 README.md
create mode 100644 hertz_studio_django_utils/README.md
create mode 100644 hertz_studio_django_utils/__init__.py
create mode 100644 hertz_studio_django_utils/code_generator/__init__.py
create mode 100644 hertz_studio_django_utils/code_generator/app_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/base_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/README.md
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/__init__.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/asgi.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/settings.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/urls.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/views.py
create mode 100644 hertz_studio_django_utils/code_generator/copy/hertz_server_django/wsgi.py
create mode 100644 hertz_studio_django_utils/code_generator/django_code_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/menu_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/model_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/serializer_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/template_engine.py
create mode 100644 hertz_studio_django_utils/code_generator/templates/.mako_modules/django/models.mako.py
create mode 100644 hertz_studio_django_utils/code_generator/templates/.mako_modules/django/serializers.mako.py
create mode 100644 hertz_studio_django_utils/code_generator/templates/.mako_modules/django/urls.mako.py
create mode 100644 hertz_studio_django_utils/code_generator/templates/.mako_modules/django/views.mako.py
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/apps.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/models.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/project_urls.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/serializers.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/settings.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/urls.mako
create mode 100644 hertz_studio_django_utils/code_generator/templates/django/views.mako
create mode 100644 hertz_studio_django_utils/code_generator/test/apps.py
create mode 100644 hertz_studio_django_utils/code_generator/test/models.py
create mode 100644 hertz_studio_django_utils/code_generator/test/serializers.py
create mode 100644 hertz_studio_django_utils/code_generator/test/urls.py
create mode 100644 hertz_studio_django_utils/code_generator/test/views.py
create mode 100644 hertz_studio_django_utils/code_generator/url_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/view_generator.py
create mode 100644 hertz_studio_django_utils/code_generator/yaml_generator_cli.py
create mode 100644 hertz_studio_django_utils/code_generator/yaml_parser.py
create mode 100644 hertz_studio_django_utils/config/__init__.py
create mode 100644 hertz_studio_django_utils/config/departments_config.py
create mode 100644 hertz_studio_django_utils/config/menus_config.py
create mode 100644 hertz_studio_django_utils/config/roles_config.py
create mode 100644 hertz_studio_django_utils/crypto/__init__.py
create mode 100644 hertz_studio_django_utils/crypto/encryption_utils.py
create mode 100644 hertz_studio_django_utils/crypto/password_hashers.py
create mode 100644 hertz_studio_django_utils/email/__init__.py
create mode 100644 hertz_studio_django_utils/email/email_service.py
create mode 100644 hertz_studio_django_utils/log/__init__.py
create mode 100644 hertz_studio_django_utils/log/log_decorator.py
create mode 100644 hertz_studio_django_utils/ollama/__init__.py
create mode 100644 hertz_studio_django_utils/ollama/ollama_client.py
create mode 100644 hertz_studio_django_utils/responses/HertzResponse.py
create mode 100644 hertz_studio_django_utils/responses/__init__.py
create mode 100644 hertz_studio_django_utils/validators/__init__.py
create mode 100644 hertz_studio_django_utils/validators/email_validator.py
create mode 100644 hertz_studio_django_utils/validators/password_validator.py
create mode 100644 hertz_studio_django_utils/validators/phone_validator.py
create mode 100644 hertz_studio_django_utils/yolo/__init__.py
create mode 100644 hertz_studio_django_utils/yolo/video_converter.py
create mode 100644 license_verifier.py
create mode 100644 requirements.txt
create mode 100644 setup.py
create mode 100644 使用手册.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e2a4543
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.c
+*.pyc
+*.pyd
+dist
+build
+.pypirc
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f974d6a
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..6b9bb88
--- /dev/null
+++ b/MANIFEST.in
@@ -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__
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/README.md b/hertz_studio_django_utils/README.md
new file mode 100644
index 0000000..220d700
--- /dev/null
+++ b/hertz_studio_django_utils/README.md
@@ -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="
HTML内容
",
+ 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 = """
+
+
+ 自定义邮件
+ 您好 {name},这是一封自定义邮件。
+
+
+""".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) 或联系开发团队。
\ No newline at end of file
diff --git a/hertz_studio_django_utils/__init__.py b/hertz_studio_django_utils/__init__.py
new file mode 100644
index 0000000..e5b8fd2
--- /dev/null
+++ b/hertz_studio_django_utils/__init__.py
@@ -0,0 +1 @@
+# Hertz Server Django Utils Package
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/__init__.py b/hertz_studio_django_utils/code_generator/__init__.py
new file mode 100644
index 0000000..7021b65
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/__init__.py
@@ -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'
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/app_generator.py b/hertz_studio_django_utils/code_generator/app_generator.py
new file mode 100644
index 0000000..d5edb84
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/app_generator.py
@@ -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)
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/base_generator.py b/hertz_studio_django_utils/code_generator/base_generator.py
new file mode 100644
index 0000000..8458a28
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/base_generator.py
@@ -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')
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/README.md b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/README.md
new file mode 100644
index 0000000..8ad7a0a
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/README.md
@@ -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文档
+
+---
+
+💡 **提示**: 此配置模块是整个项目的核心,负责协调各功能模块的协同工作。生产部署前请务必检查所有安全配置。
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/__init__.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/asgi.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/asgi.py
new file mode 100644
index 0000000..fb05dfc
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/asgi.py
@@ -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
+ )
+ )
+ ),
+})
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/settings.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/settings.py
new file mode 100644
index 0000000..83f18d7
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/settings.py
@@ -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',
+]
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/urls.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/urls.py
new file mode 100644
index 0000000..ae143a3
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/urls.py
@@ -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])
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/views.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/views.py
new file mode 100644
index 0000000..5d1f7e8
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/views.py
@@ -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')
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/copy/hertz_server_django/wsgi.py b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/wsgi.py
new file mode 100644
index 0000000..1e9fdf9
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/copy/hertz_server_django/wsgi.py
@@ -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()
diff --git a/hertz_studio_django_utils/code_generator/django_code_generator.py b/hertz_studio_django_utils/code_generator/django_code_generator.py
new file mode 100644
index 0000000..dea34f3
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/django_code_generator.py
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/menu_generator.py b/hertz_studio_django_utils/code_generator/menu_generator.py
new file mode 100644
index 0000000..b746ad3
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/menu_generator.py
@@ -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()
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/model_generator.py b/hertz_studio_django_utils/code_generator/model_generator.py
new file mode 100644
index 0000000..dba7965
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/model_generator.py
@@ -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]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/serializer_generator.py b/hertz_studio_django_utils/code_generator/serializer_generator.py
new file mode 100644
index 0000000..bc1e9b9
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/serializer_generator.py
@@ -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)
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/template_engine.py b/hertz_studio_django_utils/code_generator/template_engine.py
new file mode 100644
index 0000000..f6bf33e
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/template_engine.py
@@ -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': '模板不存在'
+ }
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/models.mako.py b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/models.mako.py
new file mode 100644
index 0000000..10688af
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/models.mako.py
@@ -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
+"""
diff --git a/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/serializers.mako.py b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/serializers.mako.py
new file mode 100644
index 0000000..38e5534
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/serializers.mako.py
@@ -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
+"""
diff --git a/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/urls.mako.py b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/urls.mako.py
new file mode 100644
index 0000000..d0386e5
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/urls.mako.py
@@ -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('/', 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('/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('/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('//', 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
+"""
diff --git a/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/views.mako.py b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/views.mako.py
new file mode 100644
index 0000000..c359948
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/.mako_modules/django/views.mako.py
@@ -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
+"""
diff --git a/hertz_studio_django_utils/code_generator/templates/django/apps.mako b/hertz_studio_django_utils/code_generator/templates/django/apps.mako
new file mode 100644
index 0000000..e477f20
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/apps.mako
@@ -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}'
+
diff --git a/hertz_studio_django_utils/code_generator/templates/django/models.mako b/hertz_studio_django_utils/code_generator/templates/django/models.mako
new file mode 100644
index 0000000..5dc0847
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/models.mako
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/django/project_urls.mako b/hertz_studio_django_utils/code_generator/templates/django/project_urls.mako
new file mode 100644
index 0000000..671840f
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/project_urls.mako
@@ -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])
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/django/serializers.mako b/hertz_studio_django_utils/code_generator/templates/django/serializers.mako
new file mode 100644
index 0000000..c42733e
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/serializers.mako
@@ -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])}]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/django/settings.mako b/hertz_studio_django_utils/code_generator/templates/django/settings.mako
new file mode 100644
index 0000000..13c12e4
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/settings.mako
@@ -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',
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/django/urls.mako b/hertz_studio_django_utils/code_generator/templates/django/urls.mako
new file mode 100644
index 0000000..94cfcd3
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/urls.mako
@@ -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('/', views.get_${snake_model_name}, name='get_${snake_model_name}'),
+ % endif
+
+ % if 'update' in operations_list:
+ # 更新${model_name}
+ path('/update/', views.update_${snake_model_name}, name='update_${snake_model_name}'),
+ % endif
+
+ % if 'delete' in operations_list:
+ # 删除${model_name}
+ path('/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}//', views.update_${snake_model_name}, name='${snake_model_name}_detail'),
+ % endif
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/templates/django/views.mako b/hertz_studio_django_utils/code_generator/templates/django/views.mako
new file mode 100644
index 0000000..b1a3163
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/templates/django/views.mako
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/test/apps.py b/hertz_studio_django_utils/code_generator/test/apps.py
new file mode 100644
index 0000000..f6a17c8
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/test/apps.py
@@ -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 = '测试应用'
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/test/models.py b/hertz_studio_django_utils/code_generator/test/models.py
new file mode 100644
index 0000000..86bba74
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/test/models.py
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/test/serializers.py b/hertz_studio_django_utils/code_generator/test/serializers.py
new file mode 100644
index 0000000..77f5dce
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/test/serializers.py
@@ -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()
diff --git a/hertz_studio_django_utils/code_generator/test/urls.py b/hertz_studio_django_utils/code_generator/test/urls.py
new file mode 100644
index 0000000..eb1c99c
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/test/urls.py
@@ -0,0 +1,6 @@
+from django.urls import path, include
+from views import *
+
+urlpatterns = [
+ path('test/', test, name='test'),
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/test/views.py b/hertz_studio_django_utils/code_generator/test/views.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/code_generator/url_generator.py b/hertz_studio_django_utils/code_generator/url_generator.py
new file mode 100644
index 0000000..7597cf3
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/url_generator.py
@@ -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()}//',
+ 'view': f'get_{model_name.lower()}',
+ 'name': f'{model_name.lower()}_detail'
+ })
+ elif operation == 'update':
+ context['url_patterns'].append({
+ 'pattern': f'{model_name.lower()}//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()}//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)
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/view_generator.py b/hertz_studio_django_utils/code_generator/view_generator.py
new file mode 100644
index 0000000..c509408
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/view_generator.py
@@ -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)
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/yaml_generator_cli.py b/hertz_studio_django_utils/code_generator/yaml_generator_cli.py
new file mode 100644
index 0000000..28a00fc
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/yaml_generator_cli.py
@@ -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())
\ No newline at end of file
diff --git a/hertz_studio_django_utils/code_generator/yaml_parser.py b/hertz_studio_django_utils/code_generator/yaml_parser.py
new file mode 100644
index 0000000..6795c6f
--- /dev/null
+++ b/hertz_studio_django_utils/code_generator/yaml_parser.py
@@ -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)
\ No newline at end of file
diff --git a/hertz_studio_django_utils/config/__init__.py b/hertz_studio_django_utils/config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/config/departments_config.py b/hertz_studio_django_utils/config/departments_config.py
new file mode 100644
index 0000000..f6258fb
--- /dev/null
+++ b/hertz_studio_django_utils/config/departments_config.py
@@ -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'
+ }
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/config/menus_config.py b/hertz_studio_django_utils/config/menus_config.py
new file mode 100644
index 0000000..0e3fc80
--- /dev/null
+++ b/hertz_studio_django_utils/config/menus_config.py
@@ -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'
+ }
+]
+
+
diff --git a/hertz_studio_django_utils/config/roles_config.py b/hertz_studio_django_utils/config/roles_config.py
new file mode 100644
index 0000000..d4aa37c
--- /dev/null
+++ b/hertz_studio_django_utils/config/roles_config.py
@@ -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
+ }
+]
\ No newline at end of file
diff --git a/hertz_studio_django_utils/crypto/__init__.py b/hertz_studio_django_utils/crypto/__init__.py
new file mode 100644
index 0000000..75b3047
--- /dev/null
+++ b/hertz_studio_django_utils/crypto/__init__.py
@@ -0,0 +1,4 @@
+from .encryption_utils import EncryptionUtils
+from .password_hashers import MD5PasswordHasher
+
+__all__ = ['EncryptionUtils', 'MD5PasswordHasher']
\ No newline at end of file
diff --git a/hertz_studio_django_utils/crypto/encryption_utils.py b/hertz_studio_django_utils/crypto/encryption_utils.py
new file mode 100644
index 0000000..e06d5e6
--- /dev/null
+++ b/hertz_studio_django_utils/crypto/encryption_utils.py
@@ -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')
\ No newline at end of file
diff --git a/hertz_studio_django_utils/crypto/password_hashers.py b/hertz_studio_django_utils/crypto/password_hashers.py
new file mode 100644
index 0000000..f8693f0
--- /dev/null
+++ b/hertz_studio_django_utils/crypto/password_hashers.py
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/email/__init__.py b/hertz_studio_django_utils/email/__init__.py
new file mode 100644
index 0000000..a87a0cb
--- /dev/null
+++ b/hertz_studio_django_utils/email/__init__.py
@@ -0,0 +1,3 @@
+from .email_service import EmailService
+
+__all__ = ['EmailService']
\ No newline at end of file
diff --git a/hertz_studio_django_utils/email/email_service.py b/hertz_studio_django_utils/email/email_service.py
new file mode 100644
index 0000000..6638a83
--- /dev/null
+++ b/hertz_studio_django_utils/email/email_service.py
@@ -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'''
+
+
+
+
+
🔐 {title}
+
+
+
亲爱的 {recipient_name},
+
{description}
+
+
+ {verification_code}
+
+
+
验证码有效期为5分钟,请尽快使用。如果您没有进行此操作,请忽略此邮件。
+
+
此致
Hertz Server Django 团队
+
+
+
+
+ '''
+
+ text_content = f'''
+ {title}
+
+ 亲爱的 {recipient_name},
+
+ {description}
+
+ 验证码:{verification_code}
+
+ 验证码有效期为5分钟,请尽快使用。如果您没有进行此操作,请忽略此邮件。
+
+ 此致
+ Hertz Server Django 团队
+ '''
+
+ return {
+ 'subject': subject,
+ 'html_content': html_content,
+ 'text_content': text_content
+ }
\ No newline at end of file
diff --git a/hertz_studio_django_utils/log/__init__.py b/hertz_studio_django_utils/log/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/log/log_decorator.py b/hertz_studio_django_utils/log/log_decorator.py
new file mode 100644
index 0000000..e4cc781
--- /dev/null
+++ b/hertz_studio_django_utils/log/log_decorator.py
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/ollama/__init__.py b/hertz_studio_django_utils/ollama/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/ollama/ollama_client.py b/hertz_studio_django_utils/ollama/ollama_client.py
new file mode 100644
index 0000000..a8a4585
--- /dev/null
+++ b/hertz_studio_django_utils/ollama/ollama_client.py
@@ -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")
\ No newline at end of file
diff --git a/hertz_studio_django_utils/responses/HertzResponse.py b/hertz_studio_django_utils/responses/HertzResponse.py
new file mode 100644
index 0000000..0b82654
--- /dev/null
+++ b/hertz_studio_django_utils/responses/HertzResponse.py
@@ -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})
\ No newline at end of file
diff --git a/hertz_studio_django_utils/responses/__init__.py b/hertz_studio_django_utils/responses/__init__.py
new file mode 100644
index 0000000..9b3b80e
--- /dev/null
+++ b/hertz_studio_django_utils/responses/__init__.py
@@ -0,0 +1,3 @@
+from .HertzResponse import HertzResponse
+
+__all__ = ['HertzResponse']
\ No newline at end of file
diff --git a/hertz_studio_django_utils/validators/__init__.py b/hertz_studio_django_utils/validators/__init__.py
new file mode 100644
index 0000000..6a254ad
--- /dev/null
+++ b/hertz_studio_django_utils/validators/__init__.py
@@ -0,0 +1,5 @@
+from .email_validator import EmailValidator
+from .phone_validator import PhoneValidator
+from .password_validator import PasswordValidator
+
+__all__ = ['EmailValidator', 'PhoneValidator', 'PasswordValidator']
\ No newline at end of file
diff --git a/hertz_studio_django_utils/validators/email_validator.py b/hertz_studio_django_utils/validators/email_validator.py
new file mode 100644
index 0000000..b4687ec
--- /dev/null
+++ b/hertz_studio_django_utils/validators/email_validator.py
@@ -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
\ No newline at end of file
diff --git a/hertz_studio_django_utils/validators/password_validator.py b/hertz_studio_django_utils/validators/password_validator.py
new file mode 100644
index 0000000..8b2a392
--- /dev/null
+++ b/hertz_studio_django_utils/validators/password_validator.py
@@ -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 "很弱"
\ No newline at end of file
diff --git a/hertz_studio_django_utils/validators/phone_validator.py b/hertz_studio_django_utils/validators/phone_validator.py
new file mode 100644
index 0000000..44b496d
--- /dev/null
+++ b/hertz_studio_django_utils/validators/phone_validator.py
@@ -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 '未知运营商'
\ No newline at end of file
diff --git a/hertz_studio_django_utils/yolo/__init__.py b/hertz_studio_django_utils/yolo/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hertz_studio_django_utils/yolo/video_converter.py b/hertz_studio_django_utils/yolo/video_converter.py
new file mode 100644
index 0000000..b120c56
--- /dev/null
+++ b/hertz_studio_django_utils/yolo/video_converter.py
@@ -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()
\ No newline at end of file
diff --git a/license_verifier.py b/license_verifier.py
new file mode 100644
index 0000000..e10451e
--- /dev/null
+++ b/license_verifier.py
@@ -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")
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e69de29
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..995a69d
--- /dev/null
+++ b/setup.py
@@ -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",
+ },
+)
diff --git a/使用手册.md b/使用手册.md
new file mode 100644
index 0000000..2567038
--- /dev/null
+++ b/使用手册.md
@@ -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目录下生成一个打包过后的文件