add ai models

This commit is contained in:
xie7654
2025-07-11 15:55:49 +08:00
parent ec30340b43
commit 62bba7d23f
9 changed files with 1541 additions and 407 deletions

View File

@@ -7,7 +7,7 @@
- admin/admin123
- chenze/admin123
演示地址https://demo.ywwuzi.cn
体验地址https://demo.ywwuzi.cn
文档地址https://docs.ywwuzi.cn

View File

@@ -18,4 +18,9 @@ class PlatformChoices(models.TextChoices):
ZHIPU = 'ZhiPu', '智谱'
MINIMAX = 'MiniMax', 'MiniMax'
MOONSHOT = 'Moonshot', '月之暗灭'
BAICHUAN = 'BaiChuan', '百川智能'
BAICHUAN = 'BaiChuan', '百川智能'
class MessageType(models.TextChoices):
USER = 'user', '用户发送'
ASSISTANT = 'assistant', '模型回复'

File diff suppressed because it is too large Load Diff

View File

@@ -1,262 +0,0 @@
# Generated by Django 5.2.1 on 2025-07-10 04:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("ai", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="aiapikey",
name="api_key",
field=models.CharField(
db_comment="密钥", max_length=255, verbose_name="密钥"
),
),
migrations.AlterField(
model_name="aiapikey",
name="create_time",
field=models.DateTimeField(
auto_now_add=True,
db_comment="创建时间",
help_text="创建时间",
null=True,
verbose_name="创建时间",
),
),
migrations.AlterField(
model_name="aiapikey",
name="creator",
field=models.CharField(
blank=True,
db_comment="创建人",
help_text="创建人",
max_length=64,
null=True,
verbose_name="创建人",
),
),
migrations.AlterField(
model_name="aiapikey",
name="is_deleted",
field=models.BooleanField(
db_comment="是否软删除", default=False, verbose_name="是否软删除"
),
),
migrations.AlterField(
model_name="aiapikey",
name="modifier",
field=models.CharField(
blank=True,
db_comment="修改人",
help_text="修改人",
max_length=64,
null=True,
verbose_name="修改人",
),
),
migrations.AlterField(
model_name="aiapikey",
name="name",
field=models.CharField(
db_comment="名称", max_length=255, verbose_name="名称"
),
),
migrations.AlterField(
model_name="aiapikey",
name="platform",
field=models.CharField(
choices=[
("AzureOpenAI", "OpenAI 微软"),
("OpenAI", "OpenAI"),
("Ollama", "Ollama"),
("YiYan", "文心一言"),
("XingHuo", "讯飞星火"),
("TongYi", "通义千问"),
("StableDiffusion", "StableDiffusion"),
("Midjourney", "Midjourney"),
("Suno", "Suno"),
("DeepSeek", "DeepSeek"),
("DouBao", "字节豆包"),
("HunYuan", "腾讯混元"),
("SiliconFlow", "硅基流动"),
("ZhiPu", "智谱"),
("MiniMax", "MiniMax"),
("Moonshot", "月之暗灭"),
("BaiChuan", "百川智能"),
],
db_comment="平台",
max_length=100,
verbose_name="平台",
),
),
migrations.AlterField(
model_name="aiapikey",
name="remark",
field=models.CharField(
blank=True,
db_comment="备注",
help_text="备注",
max_length=256,
null=True,
verbose_name="备注",
),
),
migrations.AlterField(
model_name="aiapikey",
name="update_time",
field=models.DateTimeField(
auto_now=True,
db_comment="修改时间",
help_text="修改时间",
null=True,
verbose_name="修改时间",
),
),
migrations.AlterField(
model_name="aiapikey",
name="url",
field=models.CharField(
blank=True,
db_comment="自定义 API 地址",
max_length=255,
null=True,
verbose_name="自定义 API 地址",
),
),
migrations.AlterField(
model_name="aimodel",
name="create_time",
field=models.DateTimeField(
auto_now_add=True,
db_comment="创建时间",
help_text="创建时间",
null=True,
verbose_name="创建时间",
),
),
migrations.AlterField(
model_name="aimodel",
name="creator",
field=models.CharField(
blank=True,
db_comment="创建人",
help_text="创建人",
max_length=64,
null=True,
verbose_name="创建人",
),
),
migrations.AlterField(
model_name="aimodel",
name="is_deleted",
field=models.BooleanField(
db_comment="是否软删除", default=False, verbose_name="是否软删除"
),
),
migrations.AlterField(
model_name="aimodel",
name="key",
field=models.ForeignKey(
db_comment="API 秘钥编号",
on_delete=django.db.models.deletion.CASCADE,
to="ai.aiapikey",
verbose_name="API 秘钥编号",
),
),
migrations.AlterField(
model_name="aimodel",
name="max_contexts",
field=models.IntegerField(
blank=True,
db_comment="上下文的最大 Message 数量",
null=True,
verbose_name="上下文的最大 Message 数量",
),
),
migrations.AlterField(
model_name="aimodel",
name="max_tokens",
field=models.IntegerField(
blank=True,
db_comment="单条回复的最大 Token 数量",
null=True,
verbose_name="单条回复的最大 Token 数量",
),
),
migrations.AlterField(
model_name="aimodel",
name="model",
field=models.CharField(
db_comment="模型标识", max_length=64, verbose_name="模型标识"
),
),
migrations.AlterField(
model_name="aimodel",
name="modifier",
field=models.CharField(
blank=True,
db_comment="修改人",
help_text="修改人",
max_length=64,
null=True,
verbose_name="修改人",
),
),
migrations.AlterField(
model_name="aimodel",
name="name",
field=models.CharField(
db_comment="模型名字", max_length=64, verbose_name="模型名字"
),
),
migrations.AlterField(
model_name="aimodel",
name="platform",
field=models.CharField(
db_comment="模型平台", max_length=32, verbose_name="模型平台"
),
),
migrations.AlterField(
model_name="aimodel",
name="remark",
field=models.CharField(
blank=True,
db_comment="备注",
help_text="备注",
max_length=256,
null=True,
verbose_name="备注",
),
),
migrations.AlterField(
model_name="aimodel",
name="sort",
field=models.IntegerField(
db_comment="排序", default=0, verbose_name="排序"
),
),
migrations.AlterField(
model_name="aimodel",
name="temperature",
field=models.FloatField(
blank=True, db_comment="温度参数", null=True, verbose_name="温度参数"
),
),
migrations.AlterField(
model_name="aimodel",
name="update_time",
field=models.DateTimeField(
auto_now=True,
db_comment="修改时间",
help_text="修改时间",
null=True,
verbose_name="修改时间",
),
),
]

View File

@@ -1,105 +0,0 @@
# Generated by Django 5.2.1 on 2025-07-11 02:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("ai", "0002_alter_aiapikey_api_key_alter_aiapikey_create_time_and_more"),
]
operations = [
migrations.CreateModel(
name="Tool",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"remark",
models.CharField(
blank=True,
db_comment="备注",
help_text="备注",
max_length=256,
null=True,
verbose_name="备注",
),
),
(
"creator",
models.CharField(
blank=True,
db_comment="创建人",
help_text="创建人",
max_length=64,
null=True,
verbose_name="创建人",
),
),
(
"modifier",
models.CharField(
blank=True,
db_comment="修改人",
help_text="修改人",
max_length=64,
null=True,
verbose_name="修改人",
),
),
(
"update_time",
models.DateTimeField(
auto_now=True,
db_comment="修改时间",
help_text="修改时间",
null=True,
verbose_name="修改时间",
),
),
(
"create_time",
models.DateTimeField(
auto_now_add=True,
db_comment="创建时间",
help_text="创建时间",
null=True,
verbose_name="创建时间",
),
),
(
"is_deleted",
models.BooleanField(
db_comment="是否软删除",
default=False,
verbose_name="是否软删除",
),
),
("name", models.CharField(max_length=128, verbose_name="工具名称")),
(
"description",
models.CharField(
blank=True, max_length=256, null=True, verbose_name="工具描述"
),
),
("status", models.SmallIntegerField(verbose_name="状态")),
],
options={
"verbose_name": "AI 工具",
"verbose_name_plural": "AI 工具",
"db_table": "ai_tool",
},
),
migrations.AlterModelOptions(
name="aimodel",
options={"verbose_name": "模型配置", "verbose_name_plural": "模型配置"},
),
]

View File

@@ -1,6 +1,7 @@
from django.db import models
from ai.choices import PlatformChoices
from ai.choices import PlatformChoices, MessageType
from backend import settings
from utils.models import CommonStatus, CoreModel
class AIApiKey(CoreModel):
@@ -63,9 +64,9 @@ class AIModel(CoreModel):
class Tool(CoreModel):
""" AI 工具表 """
name = models.CharField(max_length=128, verbose_name="工具名称")
description = models.CharField(max_length=256, null=True, blank=True, verbose_name="工具描述")
status = models.SmallIntegerField(verbose_name="状态")
name = models.CharField(max_length=128, verbose_name="工具名称", db_comment="工具名称")
description = models.CharField(max_length=256, null=True, blank=True, verbose_name="工具描述", db_comment="工具描述")
status = models.SmallIntegerField(verbose_name="状态", db_comment="状态", default=0)
class Meta:
db_table = "ai_tool"
@@ -73,4 +74,258 @@ class Tool(CoreModel):
verbose_name_plural = verbose_name
def __str__(self):
return self.name
return self.name
class Knowledge(CoreModel):
""" AI 知识库表 """
name = models.CharField(max_length=255, verbose_name="知识库名称", db_comment="知识库名称")
description = models.TextField(null=True, blank=True, verbose_name="知识库描述", db_comment="知识库描述")
embedding_model_id = models.ForeignKey(
'AIModel',
on_delete=models.CASCADE,
db_column='embedding_model_id',
db_comment='向量模型编号', verbose_name="向量模型编号"
)
embedding_model = models.CharField(max_length=32, verbose_name="向量模型标识", db_comment="向量模型标识")
top_k = models.IntegerField(verbose_name="topK", db_comment="topK", default=0)
similarity_threshold = models.FloatField(verbose_name="相似度阈值", db_comment="相似度阈值")
status = models.SmallIntegerField(
choices=CommonStatus.choices,
default=CommonStatus.DISABLED,
verbose_name="状态",
db_comment="状态",
)
class Meta:
db_table = "ai_knowledge"
verbose_name = "AI 知识库"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class KnowledgeDocument(CoreModel):
""" AI 知识库文档表 """
knowledge = models.ForeignKey(
Knowledge,
on_delete=models.CASCADE,
related_name="documents",
verbose_name="知识库",
db_comment="知识库"
)
name = models.CharField(max_length=255, verbose_name="文档名称", db_comment="文档名称")
url = models.CharField(max_length=1024, verbose_name="文件 URL", db_comment="文件 URL")
content = models.TextField(verbose_name="内容", db_comment="内容")
content_length = models.IntegerField(verbose_name="字符数", db_comment="字符数")
tokens = models.IntegerField(verbose_name="token 数量", db_comment="token 数量")
segment_max_tokens = models.IntegerField(verbose_name="分片最大 Token 数", db_comment="分片最大 Token 数")
retrieval_count = models.IntegerField(default=0, verbose_name="召回次数", db_comment="召回次数")
status = models.SmallIntegerField(
choices=CommonStatus.choices,
default=CommonStatus.DISABLED,
verbose_name="状态",
db_comment="状态",
)
class Meta:
db_table = "ai_knowledge_document"
verbose_name = "AI 知识库文档"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class KnowledgeSegment(CoreModel):
""" AI 知识库分段表 """
knowledge = models.ForeignKey(
Knowledge,
on_delete=models.CASCADE,
related_name="segments",
verbose_name="知识库",
db_comment="知识库"
)
document = models.ForeignKey(
KnowledgeDocument,
on_delete=models.CASCADE,
related_name="segments",
verbose_name="文档",
db_comment="文档"
)
content = models.TextField(verbose_name="分段内容", db_comment="分段内容")
content_length = models.IntegerField(verbose_name="字符数", db_comment="字符数")
tokens = models.IntegerField(verbose_name="token 数量", db_comment="token 数量")
vector_id = models.CharField(
max_length=100,
null=True, blank=True,
verbose_name="向量库的编号",
db_comment="向量库的编号"
)
retrieval_count = models.IntegerField(default=0, verbose_name="召回次数", db_comment="召回次数")
status = models.SmallIntegerField(
choices=CommonStatus.choices,
default=CommonStatus.DISABLED,
verbose_name="状态",
db_comment="状态"
)
class Meta:
db_table = "ai_knowledge_segment"
verbose_name = "AI 知识库分段"
verbose_name_plural = verbose_name
def __str__(self):
return f"Segment {self.id}"
class ChatRole(CoreModel):
""" AI 聊天角色表 """
name = models.CharField(max_length=128, verbose_name="角色名称", db_comment="角色名称")
avatar = models.CharField(max_length=256, verbose_name="头像", db_comment="头像")
description = models.CharField(max_length=256, verbose_name="角色描述", db_comment="角色描述")
status = models.SmallIntegerField(
choices=CommonStatus.choices,
default=CommonStatus.DISABLED,
verbose_name="状态",
db_comment="状态"
)
sort = models.IntegerField(default=0, verbose_name="角色排序", db_comment="角色排序")
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name="用户",
db_comment="用户编号"
)
public_status = models.BooleanField(default=False, verbose_name="是否公开", db_comment="是否公开")
category = models.CharField(max_length=32, null=True, blank=True, verbose_name="角色类别", db_comment="角色类别")
model_id = models.ForeignKey(
'AIModel',
on_delete=models.CASCADE,
db_column='model_id',
verbose_name="向量模型编号",
db_comment='向量模型编号'
)
system_message = models.CharField(
max_length=1024, null=True, blank=True,
verbose_name="角色上下文",
db_comment="角色上下文"
)
knowledge = models.ManyToManyField(
'Knowledge',
blank=True,
related_name="roles",
verbose_name="关联的知识库",
db_comment="关联的知识库"
)
tools = models.ManyToManyField(
'Tool',
blank=True,
related_name="roles",
verbose_name="关联的工具",
db_comment="关联的工具"
)
class Meta:
db_table = "ai_chat_role"
verbose_name = "AI 聊天角色"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class ChatConversation(CoreModel):
""" AI 聊天对话表 """
title = models.CharField(max_length=256, verbose_name="对话标题", db_comment="对话标题")
pinned = models.BooleanField(default=False, verbose_name="是否置顶", db_comment="是否置顶")
pinned_time = models.DateTimeField(null=True, blank=True, verbose_name="置顶时间", db_comment="置顶时间")
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name="用户",
db_comment="用户编号"
)
role = models.ForeignKey(
'ChatRole',
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name="聊天角色",
db_comment="聊天角色"
)
model_id = models.ForeignKey(
'AIModel',
on_delete=models.CASCADE,
db_column='model_id',
verbose_name="向量模型编号",
db_comment='向量模型编号'
)
model = models.CharField(max_length=32, verbose_name="模型标识", db_comment="模型标识")
system_message = models.CharField(
max_length=1024, null=True, blank=True,
verbose_name="角色设定",
db_comment="角色设定"
)
temperature = models.FloatField(verbose_name="温度参数", db_comment="温度参数")
max_tokens = models.IntegerField(verbose_name="单条回复的最大 Token 数量", db_comment="单条回复的最大 Token 数量")
max_contexts = models.IntegerField(verbose_name="上下文的最大 Message 数量", db_comment="上下文的最大 Message 数量")
class Meta:
db_table = "ai_chat_conversation"
verbose_name = "AI 聊天对话"
verbose_name_plural = verbose_name
def __str__(self):
return self.title
class ChatMessage(CoreModel):
""" AI 聊天消息表 """
conversation_id = models.BigIntegerField(verbose_name="对话编号", db_comment="对话编号")
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name="用户",
db_comment="用户编号"
)
role = models.ForeignKey(
'ChatRole',
null=True, blank=True,
on_delete=models.SET_NULL,
verbose_name="聊天角色",
db_comment="聊天角色"
)
model = models.CharField(max_length=32, verbose_name="模型标识", db_comment="模型标识")
model_id = models.ForeignKey(
'AIModel',
on_delete=models.CASCADE,
db_column='model_id',
verbose_name="向量模型编号",
db_comment='向量模型编号'
)
type = models.CharField(
max_length=16,
choices=MessageType.choices,
verbose_name="消息类型",
db_comment="消息类型",
)
reply_id = models.BigIntegerField(null=True, blank=True, verbose_name="回复编号", db_comment="回复编号")
content = models.CharField(max_length=2048, verbose_name="消息内容", db_comment="消息内容")
use_context = models.BooleanField(default=False, verbose_name="是否携带上下文", db_comment="是否携带上下文")
segment_ids = models.CharField(
max_length=2048, null=True, blank=True,
verbose_name="段落编号数组",
db_comment="段落编号数组"
)
class Meta:
db_table = "ai_chat_message"
verbose_name = "AI 聊天消息"
verbose_name_plural = verbose_name
def __str__(self):
return self.content[:30]

View File

@@ -173,11 +173,9 @@ class Command(BaseCommand):
field_config = self.generate_grid_form_field(field)
if field_config:
grid_form_fields.append(field_config)
# 生成 useColumns
columns = []
for field in business_fields + core_fields:
columns.append(f" {{\n field: '{field.name}',\n title: '{getattr(field, 'verbose_name', field.name)}',\n }},")
columns = self.get_columns_code(business_fields + core_fields)
context = get_context(app_name, model_name, model, model_name_snake)
context['form_fields'] = '\n'.join(form_fields)
context['grid_form_fields'] = '\n'.join(grid_form_fields)
@@ -218,28 +216,41 @@ class Command(BaseCommand):
return 'any'
def generate_form_field(self, field):
"""生成表单字段配置"""
field_name = field.name
field_label = getattr(field, 'verbose_name', field_name)
if field_name == 'status':
return "{ component: 'RadioGroup', componentProps: { buttonStyle: 'solid', options: [{ label: $t('common.enabled'), value: 1 }, { label: $t('common.disabled'), value: 0 }], optionType: 'button' }, defaultValue: 1, fieldName: 'status', label: $t('system.status') },"
if isinstance(field, models.CharField):
return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n rules: z\n .string()\n .min(1, $t('ui.formRules.required', ['{field_label}']))\n .max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])),\n }},'''
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}', rules: z.string().min(1, $t('ui.formRules.required', ['{field_label}'])).max(100, $t('ui.formRules.maxLength', ['{field_label}', 100])) }},"
elif isinstance(field, models.TextField):
return f''' {{\n component: 'Input',\n componentProps: {{\n rows: 3,\n showCount: true,\n }},\n fieldName: '{field_name}',\n label: '{field_label}',\n rules: z\n .string()\n .max(500, $t('ui.formRules.maxLength', ['{field_label}', 500]))\n .optional(),\n }},'''
return f"{{ component: 'Input', componentProps: {{ rows: 3, showCount: true }}, fieldName: '{field_name}', label: '{field_label}', rules: z.string().max(500, $t('ui.formRules.maxLength', ['{field_label}', 500])).optional() }},"
elif isinstance(field, models.IntegerField):
return f''' {{\n component: 'InputNumber',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
return f"{{ component: 'InputNumber', fieldName: '{field_name}', label: '{field_label}' }},"
elif isinstance(field, models.BooleanField):
return f''' {{\n component: 'RadioGroup',\n componentProps: {{\n buttonStyle: 'solid',\n options: [\n {{ label: '开启', value: 1 }},\n {{ label: '关闭', value: 0 }},\n ],\n optionType: 'button',\n }},\n defaultValue: 1,\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
return f"{{ component: 'RadioGroup', componentProps: {{ buttonStyle: 'solid', options: [{{ label: '开启', value: 1 }}, {{ label: '关闭', value: 0 }}], optionType: 'button' }}, defaultValue: 1, fieldName: '{field_name}', label: '{field_label}' }},"
else:
return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
def generate_grid_form_field(self, field):
field_name = field.name
field_label = getattr(field, 'verbose_name', field_name)
# 查询表单一般只需要 component/fieldName/label不需要 rules
if field_name == 'status':
return "{ component: 'Select', fieldName: 'status', label: '状态', componentProps: { allowClear: true, options: [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }] } },"
if isinstance(field, models.CharField):
return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
elif isinstance(field, models.IntegerField):
return f''' {{\n component: 'InputNumber',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
# 其他类型同理
return f"{{ component: 'InputNumber', fieldName: '{field_name}', label: '{field_label}' }},"
else:
return f''' {{\n component: 'Input',\n fieldName: '{field_name}',\n label: '{field_label}',\n }},'''
return f"{{ component: 'Input', fieldName: '{field_name}', label: '{field_label}' }},"
def get_columns_code(self, fields):
columns = []
for field in fields:
if field.name == 'status':
columns.append("{ field: 'status', title: '状态', cellRender: { name: 'CellTag' } },")
continue
if isinstance(field, (models.DateField, models.DateTimeField)):
columns.append(f"{{ field: '{field.name}', title: '{getattr(field, 'verbose_name', field.name)}', width: 150, formatter: ({{ cellValue }}) => format_datetime(cellValue) }},")
continue
columns.append(f"{{ field: '{field.name}', title: '{getattr(field, 'verbose_name', field.name)}' }},")
return columns

View File

@@ -1,5 +1,7 @@
# Generated by Django 5.2.1 on 2025-07-10 04:07
# Generated by Django 5.2.1 on 2025-07-11 07:44
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
@@ -572,6 +574,45 @@ class Migration(migrations.Migration):
verbose_name="创建人",
),
),
migrations.AlterField(
model_name="user",
name="date_joined",
field=models.DateTimeField(
db_comment="date joined",
default=django.utils.timezone.now,
verbose_name="date joined",
),
),
migrations.AlterField(
model_name="user",
name="email",
field=models.EmailField(
blank=True,
db_comment="email address",
max_length=254,
verbose_name="email address",
),
),
migrations.AlterField(
model_name="user",
name="first_name",
field=models.CharField(
blank=True,
db_comment="first name",
max_length=150,
verbose_name="first name",
),
),
migrations.AlterField(
model_name="user",
name="is_active",
field=models.BooleanField(
db_comment="active",
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
migrations.AlterField(
model_name="user",
name="is_deleted",
@@ -579,6 +620,46 @@ class Migration(migrations.Migration):
db_comment="是否软删除", default=False, verbose_name="是否软删除"
),
),
migrations.AlterField(
model_name="user",
name="is_staff",
field=models.BooleanField(
db_comment="staff status",
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
migrations.AlterField(
model_name="user",
name="is_superuser",
field=models.BooleanField(
db_comment="superuser status",
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
migrations.AlterField(
model_name="user",
name="last_login",
field=models.DateTimeField(
blank=True,
db_comment="last login",
null=True,
verbose_name="last login",
),
),
migrations.AlterField(
model_name="user",
name="last_name",
field=models.CharField(
blank=True,
db_comment="last name",
max_length=150,
verbose_name="last name",
),
),
migrations.AlterField(
model_name="user",
name="modifier",
@@ -591,6 +672,13 @@ class Migration(migrations.Migration):
verbose_name="修改人",
),
),
migrations.AlterField(
model_name="user",
name="password",
field=models.CharField(
db_comment="password", max_length=128, verbose_name="password"
),
),
migrations.AlterField(
model_name="user",
name="remark",
@@ -614,4 +702,17 @@ class Migration(migrations.Migration):
verbose_name="修改时间",
),
),
migrations.AlterField(
model_name="user",
name="username",
field=models.CharField(
db_comment="username",
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
]

View File

@@ -9,7 +9,20 @@ class CommonStatus(models.IntegerChoices):
DISABLED = 0, '禁用'
ENABLED = 1, '启用'
class CoreModel(models.Model):
class AutoCommentModel(models.Model):
class Meta:
abstract = True
# 给字段自动添加db_comment
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for field in cls._meta.fields:
if getattr(field, 'verbose_name', None) and not getattr(field, 'db_comment', None):
field.db_comment = field.verbose_name
class CoreModel(AutoCommentModel):
remark = models.CharField(max_length=256, db_comment="备注", null=True, blank=True, help_text="备注",
verbose_name="备注")
creator = models.CharField(max_length=64, null=True, blank=True, help_text="创建人", db_comment="创建人",