daren_project/user_management/models.py

952 lines
40 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from itertools import count
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
import uuid
import logging
from django.contrib.auth import get_user_model
logger = logging.getLogger(__name__)
class User(AbstractUser):
"""自定义用户模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
ROLE_CHOICES = (
('admin', '管理员'),
('leader', '组长'),
('member', '组员'),
)
name = models.CharField(max_length=150, verbose_name='真实姓名', default='未设置')
role = models.CharField(max_length=20, choices=ROLE_CHOICES, verbose_name='角色', default='member')
department = models.CharField(max_length=100, verbose_name='部门', default='未分配')
group = models.CharField(max_length=100, null=True, blank=True, verbose_name='小组')
class Meta:
db_table = 'users'
verbose_name = '用户'
verbose_name_plural = '用户'
def __str__(self):
return f"{self.username}({self.name})"
def can_manage_department(self):
"""检查是否可以管理部门"""
return self.role in ['admin', 'leader']
def can_manage_knowledge_base(self, knowledge_base):
"""检查是否可以管理知识库"""
if self.role == 'admin':
return knowledge_base.type != 'private' # 管理员不能管理私人知识库
if self.role == 'leader' and self.department == knowledge_base.department:
return knowledge_base.type == 'member' # 组长只能管理本部门的成员知识库
return knowledge_base.user_id == str(self.id) # 用户可以管理自己创建的知识库
def has_access_permission(self, knowledge_base):
"""检查用户是否有权限访问知识库"""
# 1. 如果是私人知识库
if knowledge_base.type == 'private':
# 创建者直接允许
if str(knowledge_base.user_id) == str(self.id):
return True
# 其他人需要申请权限
return Permission.objects.filter(
resource_type='knowledge',
resource_id=str(knowledge_base.id),
applicant=self,
status='approved',
expires_at__gt=timezone.now()
).exists()
# 2. 如果是管理级知识库
if knowledge_base.type == 'admin':
# 管理员直接允许
if self.role == 'admin':
return True
# 其他人需要申请权限
return Permission.objects.filter(
resource_type='knowledge',
resource_id=str(knowledge_base.id),
applicant=self,
status='approved'
).exists()
# 3. 如果是部门级知识库
if knowledge_base.type == 'leader':
# 同部门的管理员和组长可以访问
if self.department == knowledge_base.department:
return self.role in ['admin', 'leader']
return False
# 4. 如果是成员级知识库
if knowledge_base.type == 'member':
# 同部门的所有人可以访问
return self.department == knowledge_base.department
class Data(models.Model):
"""统一的数据模型"""
DATA_TYPES = (
('admin', '管理员数据'),
('leader', '组长数据'),
('member', '组员数据'),
)
# 基本信息
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100, help_text="数据名称")
desc = models.TextField(blank=True, help_text="数据描述")
type = models.CharField(max_length=10, choices=DATA_TYPES)
meta = models.JSONField(default=dict, blank=True, help_text="元数据")
# 关联信息
user_id = models.UUIDField(help_text="创建者ID")
department = models.CharField(max_length=50, blank=True)
# 统计信息
char_length = models.IntegerField(null=True, blank=True, help_text="字符长度")
document_count = models.IntegerField(null=True, blank=True, help_text="文档数量")
application_mapping_count = models.IntegerField(default=0, help_text="应用映射数量")
# 时间信息
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'user_data'
indexes = [
models.Index(fields=['type', 'user_id']),
models.Index(fields=['create_time']),
]
def __str__(self):
return f"{self.name} ({self.get_type_display()})"
def publish(self):
"""发布数据"""
if self.status == 'draft':
self.status = 'published'
self.published_at = timezone.now()
self.save()
logger.info(f"Data {self.id} published by {self.owner.username}")
def archive(self):
"""归档数据"""
if self.status == 'published':
self.status = 'archived'
self.archived_at = timezone.now()
self.save()
logger.info(f"Data {self.id} archived by {self.owner.username}")
class Notification(models.Model):
"""通知模型"""
NOTIFICATION_TYPES = (
('permission_request', '权限申请'),
('permission_approved', '权限批准'),
('permission_rejected', '权限拒绝'),
('permission_expired', '权限过期'),
('system_notice', '系统通知'),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
type = models.CharField(max_length=20, choices=NOTIFICATION_TYPES)
title = models.CharField(max_length=100)
content = models.TextField()
sender = models.ForeignKey('User', on_delete=models.CASCADE, related_name='sent_notifications')
receiver = models.ForeignKey('User', on_delete=models.CASCADE, related_name='received_notifications')
is_read = models.BooleanField(default=False)
related_resource = models.CharField(max_length=100, blank=True) # 相关资源ID
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['receiver', '-created_at']),
models.Index(fields=['type', 'is_read']),
]
def __str__(self):
return f"{self.get_type_display()} - {self.title}"
class Permission(models.Model):
"""权限申请模型"""
STATUS_CHOICES = (
('pending', '待审批'),
('approved', '已批准'),
('rejected', '已拒绝'),
)
knowledge_base = models.ForeignKey(
'KnowledgeBase',
on_delete=models.CASCADE,
related_name='permissions',
verbose_name='知识库'
)
applicant = models.ForeignKey(
'User',
on_delete=models.CASCADE,
related_name='permission_applications',
verbose_name='申请人'
)
approver = models.ForeignKey(
'User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='permission_approvals',
verbose_name='审批人'
)
permissions = models.JSONField(default=dict, verbose_name='权限配置') # {"can_read": true, "can_edit": false, "can_delete": false}
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='状态')
reason = models.TextField(verbose_name='申请原因')
response_message = models.TextField(null=True, blank=True, verbose_name='审批意见')
expires_at = models.DateTimeField(null=True, blank=True, verbose_name='过期时间') # 修改为可空
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '权限申请'
verbose_name_plural = '权限申请'
ordering = ['-created_at']
def __str__(self):
return f"{self.applicant} 申请 {self.knowledge_base} 的权限"
def clean(self):
"""验证权限申请的合法性"""
try:
# 检查是否是自己的知识库
if str(self.knowledge_base.user_id) == str(self.applicant.id):
raise ValidationError('不能申请访问自己的知识库')
except KnowledgeBase.DoesNotExist:
raise ValidationError('知识库不存在')
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
def approve(self, approver, response_message=''):
"""批准权限申请"""
if self.status == 'pending':
self.status = 'approved'
self.approver = approver
self.response_message = response_message
self.save()
def reject(self, approver, response_message=''):
"""拒绝权限申请"""
if self.status == 'pending':
self.status = 'rejected'
self.approver = approver
self.response_message = response_message
self.save()
def check_expiration(self):
"""检查权限是否过期"""
if self.status == 'approved' and self.expires_at and self.expires_at < timezone.now():
self.status = 'expired'
self.save()
logger.info(f"Permission {self.id} expired")
def send_notification(self, notification_type, title, content):
"""发送通知"""
Notification.objects.create(
type=notification_type,
title=title,
content=content,
sender=self.applicant,
receiver=self.approver if notification_type == 'permission_request' else self.applicant,
related_resource=str(self.id)
)
def notify_approver(self):
"""通知审批人"""
self.send_notification(
'permission_request',
f'新的权限申请 - {self.get_resource_type_display()}',
f'用户 {self.applicant.username} 申请访问 {self.get_resource_type_display()} 的权限'
)
def notify_applicant(self, status):
"""通知申请人审批结果"""
notification_type = 'permission_approved' if status == 'approved' else 'permission_rejected'
title = f'权限申请{self.get_status_display()} - {self.get_resource_type_display()}'
content = f'您申请的 {self.get_resource_type_display()} 权限已{self.get_status_display()}'
self.send_notification(notification_type, title, content)
class ChatHistory(models.Model):
"""聊天历史记录"""
ROLE_CHOICES = [
('user', '用户'),
('assistant', 'AI助手'),
('system', '系统')
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
# 保留与主知识库的关联
knowledge_base = models.ForeignKey('KnowledgeBase', on_delete=models.CASCADE)
# 用于标识知识库组合的对话
conversation_id = models.CharField(max_length=100, db_index=True)
# 对话标题
title = models.CharField(max_length=100, null=True, blank=True, default='New chat', help_text="对话标题")
parent_id = models.CharField(max_length=100, null=True, blank=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
content = models.TextField()
tokens = models.IntegerField(default=0, help_text="消息token数")
# 扩展metadata字段用于存储知识库组合信息
metadata = models.JSONField(default=dict, blank=True, help_text="""
{
'model_id': 'xxx',
'dataset_id_list': ['id1', 'id2', ...],
'dataset_external_id_list': ['ext1', 'ext2', ...],
'primary_knowledge_base': 'id1'
}
""")
created_at = models.DateTimeField(auto_now_add=True)
is_deleted = models.BooleanField(default=False)
class Meta:
db_table = 'chat_history'
ordering = ['created_at']
indexes = [
models.Index(fields=['conversation_id', 'created_at']),
models.Index(fields=['user', 'created_at']),
# 添加新的索引以支持知识库组合查询
models.Index(fields=['conversation_id', 'is_deleted']),
]
def __str__(self):
return f"{self.user.username} - {self.knowledge_base.name} - {self.created_at}"
@classmethod
def get_conversation(cls, conversation_id):
"""获取完整对话历史"""
return cls.objects.filter(
conversation_id=conversation_id,
is_deleted=False
).order_by('created_at')
@classmethod
def get_conversations_by_knowledge_bases(cls, dataset_ids, user):
"""根据知识库组合获取对话历史"""
# 对知识库ID列表排序以确保一致性
sorted_kb_ids = sorted(dataset_ids)
conversation_id = str(uuid.uuid5(
uuid.NAMESPACE_DNS,
'-'.join(sorted_kb_ids)
))
return cls.objects.filter(
conversation_id=conversation_id,
user=user,
is_deleted=False
).order_by('created_at')
@classmethod
def get_knowledge_base_combinations(cls, user):
"""获取用户的所有知识库组合"""
return cls.objects.filter(
user=user,
is_deleted=False
).values('conversation_id').annotate(
last_message=max('created_at'),
message_count=count('id')
).values(
'conversation_id',
'last_message',
'message_count',
'metadata'
).order_by('-last_message')
def get_knowledge_bases(self):
"""获取此消息关联的所有知识库"""
if self.metadata and 'dataset_id_list' in self.metadata:
return KnowledgeBase.objects.filter(
id__in=self.metadata['dataset_id_list']
)
return KnowledgeBase.objects.filter(id=self.knowledge_base.id)
def soft_delete(self):
"""软删除消息"""
self.is_deleted = True
self.save()
def to_dict(self):
"""转换为字典格式"""
return {
'id': str(self.id),
'conversation_id': self.conversation_id,
'role': self.role,
'content': self.content,
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'metadata': self.metadata,
'knowledge_bases': [
{
'id': str(kb.id),
'name': kb.name,
'type': kb.type
} for kb in self.get_knowledge_bases()
]
}
class UserProfile(models.Model):
"""用户档案模型"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
department = models.CharField(max_length=100, blank=True, help_text="部门")
group = models.CharField(max_length=100, blank=True, help_text="小组")
auto_recommend_reply = models.BooleanField(default=False, help_text="是否启用自动推荐回复功能")
class Meta:
db_table = 'user_profiles'
def __str__(self):
return f"{self.user.username}的个人资料"
class KnowledgeBasePermission(models.Model):
"""知识库权限模型 - 实现知识库和用户的多对多关系"""
STATUS_CHOICES = (
('active', '生效中'),
('expired', '已过期'),
('revoked', '已撤销'),
)
knowledge_base = models.ForeignKey(
'KnowledgeBase',
on_delete=models.CASCADE,
related_name='user_permissions',
verbose_name='知识库'
)
user = models.ForeignKey(
'User',
on_delete=models.CASCADE,
related_name='knowledge_base_permissions',
verbose_name='用户'
)
# 基础权限
can_read = models.BooleanField(default=False, verbose_name='查看权限')
can_edit = models.BooleanField(default=False, verbose_name='修改权限')
can_delete = models.BooleanField(default=False, verbose_name='删除权限')
# 权限状态
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
default='active',
verbose_name='状态'
)
# 授权信息
granted_by = models.ForeignKey(
'User',
on_delete=models.SET_NULL,
null=True,
related_name='granted_permissions',
verbose_name='授权人'
)
granted_at = models.DateTimeField(auto_now_add=True, verbose_name='授权时间')
expires_at = models.DateTimeField(null=True, blank=True, verbose_name='过期时间')
class Meta:
app_label = 'user_management'
db_table = 'knowledge_base_permissions'
unique_together = ['knowledge_base', 'user']
indexes = [
models.Index(fields=['knowledge_base', 'user', 'status']),
]
verbose_name = '知识库权限'
verbose_name_plural = '知识库权限'
def __str__(self):
return f"{self.user.username} - {self.knowledge_base.name}"
def is_valid(self):
"""检查权限是否有效"""
if self.status != 'active':
return False
if self.expires_at and self.expires_at < timezone.now():
self.status = 'expired'
self.save()
return False
return True
class KnowledgeBase(models.Model):
"""知识库模型"""
KNOWLEDGE_BASE_TYPES = [
('admin', '管理级知识库'),
('leader', '部门级知识库'),
('member', '成员级知识库'),
('private', '私有知识库'),
('secret', '公司级别私密知识库'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user_id = models.UUIDField(verbose_name='创建者ID')
name = models.CharField(max_length=100, unique=True, verbose_name='知识库名称')
desc = models.TextField(verbose_name='知识库描述', null=True, blank=True)
type = models.CharField(
max_length=20,
choices=KNOWLEDGE_BASE_TYPES,
default='private',
verbose_name='知识库类型'
)
department = models.CharField(max_length=50, null=True, blank=True)
group = models.CharField(max_length=50, null=True, blank=True)
documents = models.JSONField(default=list)
char_length = models.IntegerField(default=0)
document_count = models.IntegerField(default=0)
external_id = models.UUIDField(null=True, blank=True)
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
def is_owner(self, user):
"""检查用户是否是所有者(通过权限表检查)"""
return str(user.id) == str(self.user_id) or KnowledgeBasePermission.objects.filter(
knowledge_base=self,
user=user,
can_read=True,
can_edit=True,
can_delete=True,
status='active'
).exists()
def get_owners(self):
"""获取所有所有者(包括创建者和具有完整权限的用户)"""
from .models import User
# 获取创建者
owners = [self.user_id]
# 获取具有完整权限的用户
permission_owners = KnowledgeBasePermission.objects.filter(
knowledge_base=self,
can_read=True,
can_edit=True,
can_delete=True,
status='active'
).values_list('user_id', flat=True)
owners.extend(permission_owners)
return User.objects.filter(id__in=set(owners))
def calculate_stats(self):
"""计算文档统计信息"""
total_chars = 0
doc_count = 0
if self.documents:
doc_count = len(self.documents)
for doc in self.documents:
if 'paragraphs' in doc:
for para in doc['paragraphs']:
if 'content' in para:
total_chars += len(para['content'])
if 'title' in para:
total_chars += len(para['title'])
return doc_count, total_chars
def save(self, *args, **kwargs):
"""重写保存方法"""
# 只在创建时计算统计信息
if not self.pk: # 如果是新实例
doc_count, char_count = self.calculate_stats()
self.document_count = doc_count
self.char_length = char_count
# 直接调用Model的save方法
models.Model.save(self, *args, **kwargs)
@classmethod
def update_external_id(cls, instance_id, external_id):
"""更新外部ID的静态方法"""
cls.objects.filter(id=instance_id).update(external_id=external_id)
class Meta:
db_table = 'knowledge_bases'
indexes = [
models.Index(fields=['type']),
models.Index(fields=['department']),
models.Index(fields=['group'])
]
def __str__(self):
return f"{self.name} ({self.get_type_display()})"
def clean(self):
"""验证知识库类型与创建者权限是否匹配"""
try:
user = User.objects.get(id=self.user_id)
if user.role == 'member' and self.type != 'private':
raise ValidationError('组员只能创建私人知识库')
if user.role == 'leader' and self.type not in ['member', 'private']:
raise ValidationError('组长只能创建成员级或私人知识库')
except User.DoesNotExist:
raise ValidationError('创建者不存在')
def to_response_dict(self):
"""转换为API响应格式"""
return {
"create_time": self.create_time.isoformat(),
"update_time": self.update_time.isoformat(),
"id": str(self.id),
"name": self.name,
"desc": self.desc,
"type": self.type,
"meta": self.meta,
"user_id": str(self.user_id),
"embedding_mode_id": str(self.embedding_mode_id),
"char_length": self.char_length,
"application_mapping_count": self.application_mapping_count,
"document_count": self.document_count
}
def to_external_format(self):
"""转换为外部接口格式"""
return {
"name": self.name,
"desc": self.desc,
"documents": self.documents
}
@classmethod
def from_external_format(cls, data, user_id, embedding_mode_id):
"""从外部接口格式创建实例"""
return cls(
name=data["name"],
desc=data["desc"],
documents=data["documents"],
user_id=user_id,
embedding_mode_id=embedding_mode_id,
document_count=len(data["documents"]) if data.get("documents") else 0
)
class FeishuCreator(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
record_id = models.CharField(max_length=100, unique=True, verbose_name='飞书记录ID')
# 对接人信息
contact_person = models.CharField(max_length=50, blank=True, verbose_name='对接人')
# 基本账号信息
handle = models.TextField(blank=True, verbose_name='Handle')
tiktok_url = models.TextField(blank=True, verbose_name='链接')
fans_count = models.CharField(max_length=50, blank=True, verbose_name='粉丝数')
gmv = models.CharField(max_length=100, blank=True, verbose_name='GMV')
# 联系方式
email = models.EmailField(blank=True, verbose_name='邮箱')
phone = models.CharField(max_length=50, blank=True, verbose_name='手机号|WhatsApp')
# 账号属性和报价
account_type = models.CharField(max_length=50, blank=True, verbose_name='账号属性')
price_quote = models.TextField(blank=True, verbose_name='报价')
response_speed = models.CharField(max_length=50, blank=True, verbose_name='回复速度')
# 合作相关
cooperation_intention = models.CharField(max_length=50, blank=True, verbose_name='合作意向')
payment_method = models.CharField(max_length=50, blank=True, verbose_name='支付方式')
payment_account = models.CharField(max_length=100, blank=True, verbose_name='收款账号')
address = models.TextField(blank=True, verbose_name='收件地址')
has_ooin = models.CharField(max_length=10, blank=True, verbose_name='签约OOIN?')
# 渠道和进度
source = models.CharField(max_length=100, blank=True, verbose_name='渠道来源')
contact_status = models.CharField(max_length=50, blank=True, verbose_name='建联进度')
cooperation_brands = models.JSONField(default=list, blank=True, verbose_name='合作品牌')
# 品类信息
system_categories = models.CharField(max_length=100, blank=True, verbose_name='系统展示的带货品类')
actual_categories = models.CharField(max_length=100, blank=True, verbose_name='实际高播放量带货品类')
human_categories = models.CharField(max_length=100, blank=True, verbose_name='达人标想要货品类')
# 其他信息
creator_base = models.CharField(max_length=100, blank=True, verbose_name='达人base')
notes = models.TextField(blank=True, verbose_name='父记录')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'feishu_creators'
verbose_name = '创作者数据'
verbose_name_plural = '创作者数据'
class KnowledgeBaseDocument(models.Model):
"""知识库文档关联模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
knowledge_base = models.ForeignKey(
'KnowledgeBase',
on_delete=models.CASCADE,
related_name='kb_documents',
verbose_name='知识库'
)
document_id = models.CharField(max_length=100, verbose_name='文档ID')
document_name = models.CharField(max_length=255, verbose_name='文档名称')
external_id = models.CharField(max_length=100, verbose_name='外部文档ID')
uploader_name = models.CharField(max_length=100, default="未知用户", verbose_name='上传者姓名')
status = models.CharField(
max_length=20,
default='active',
choices=[
('active', '有效'),
('deleted', '已删除')
],
verbose_name='状态'
)
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'knowledge_base_documents'
unique_together = ['knowledge_base', 'document_id']
indexes = [
models.Index(fields=['knowledge_base', 'status']),
models.Index(fields=['document_id']),
models.Index(fields=['external_id'])
]
verbose_name = '知识库文档'
verbose_name_plural = '知识库文档'
def __str__(self):
return f"{self.knowledge_base.name} - {self.document_name}"
class GmailCredential(models.Model):
"""Gmail账号凭证"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_credentials')
gmail_email = models.EmailField(verbose_name='Gmail邮箱', max_length=255, default='your_default_email@example.com')
name = models.CharField(verbose_name='名称', max_length=100, default='默认Gmail')
credentials = models.TextField(verbose_name='凭证JSON', blank=True, null=True)
token_path = models.CharField(verbose_name='令牌路径', max_length=255, blank=True, null=True)
is_default = models.BooleanField(verbose_name='是否默认', default=False)
last_history_id = models.CharField(verbose_name='最后历史ID', max_length=100, blank=True, null=True)
watch_expiration = models.DateTimeField(verbose_name='监听过期时间', blank=True, null=True)
is_active = models.BooleanField(verbose_name='是否活跃', default=True)
created_at = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新时间', auto_now=True)
gmail_credential_id = models.CharField(verbose_name='Gmail凭证ID', max_length=255, blank=True, null=True)
needs_reauth = models.BooleanField(verbose_name='需要重新授权', default=False)
def __str__(self):
return f"{self.name} ({self.gmail_email})"
class Meta:
verbose_name = 'Gmail凭证'
verbose_name_plural = 'Gmail凭证'
unique_together = ('user', 'gmail_email')
ordering = ['-is_default', '-updated_at']
class GmailTalentMapping(models.Model):
"""Gmail达人映射关系模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_talent_mappings')
talent_email = models.EmailField(verbose_name='达人邮箱')
knowledge_base = models.ForeignKey(KnowledgeBase, on_delete=models.CASCADE, related_name='gmail_mappings')
conversation_id = models.CharField(max_length=100, verbose_name='对话ID')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'gmail_talent_mappings'
unique_together = ['user', 'talent_email']
verbose_name = 'Gmail达人映射'
verbose_name_plural = 'Gmail达人映射'
def __str__(self):
return f"{self.user.username} - {self.talent_email}"
class GmailAttachment(models.Model):
"""Gmail附件模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
chat_message = models.ForeignKey(ChatHistory, on_delete=models.CASCADE, related_name='gmail_attachments')
gmail_message_id = models.CharField(max_length=100, verbose_name='Gmail消息ID')
filename = models.CharField(max_length=255, verbose_name='文件名')
filepath = models.CharField(max_length=500, verbose_name='文件路径')
mimetype = models.CharField(max_length=100, verbose_name='MIME类型')
filesize = models.IntegerField(default=0, verbose_name='文件大小')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
db_table = 'gmail_attachments'
verbose_name = 'Gmail附件'
verbose_name_plural = 'Gmail附件'
def __str__(self):
return f"{self.filename} ({self.filesize} bytes)"
class UserGoal(models.Model):
"""用户总目标模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='goals')
content = models.TextField(verbose_name='总目标内容')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'user_goals'
verbose_name = '用户总目标'
verbose_name_plural = '用户总目标'
def __str__(self):
return f"{self.user.username}的总目标 - {self.content[:50]}..."
class ConversationSummary(models.Model):
"""对话总结模型"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='conversation_summaries')
talent_email = models.EmailField(verbose_name='达人邮箱')
conversation_id = models.CharField(max_length=100, verbose_name='对话ID')
summary = models.TextField(verbose_name='对话总结')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'conversation_summaries'
verbose_name = '对话总结'
verbose_name_plural = '对话总结'
def __str__(self):
return f"{self.user.username}{self.talent_email}的对话总结"
class OperatorAccount(models.Model):
"""运营账号信息表"""
id = models.AutoField(primary_key=True) # 保留自动递增的ID字段
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')
POSITION_CHOICES = [
('editor', '编辑'),
('planner', '策划'),
('operator', '运营'),
('admin', '管理员'),
]
username = models.CharField(max_length=100, unique=True, verbose_name='用户名')
password = models.CharField(max_length=255, verbose_name='密码')
real_name = models.CharField(max_length=50, verbose_name='真实姓名')
email = models.EmailField(verbose_name='邮箱')
phone = models.CharField(max_length=15, verbose_name='电话')
position = models.CharField(max_length=20, choices=POSITION_CHOICES, verbose_name='工作定位')
department = models.CharField(max_length=50, verbose_name='部门')
is_active = models.BooleanField(default=True, verbose_name='是否在职')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '运营账号'
verbose_name_plural = '运营账号'
def __str__(self):
return f"{self.real_name} ({self.username})"
class PlatformAccount(models.Model):
"""平台账号信息表"""
STATUS_CHOICES = [
('active', '正常'),
('restricted', '限流'),
('suspended', '封禁'),
('inactive', '未激活'),
]
PLATFORM_CHOICES = [
('youtube', 'YouTube'),
('tiktok', 'TikTok'),
('twitter', 'Twitter/X'),
('instagram', 'Instagram'),
('facebook', 'Facebook'),
('bilibili', 'Bilibili'),
]
operator = models.ForeignKey(OperatorAccount, on_delete=models.CASCADE, related_name='platform_accounts', verbose_name='关联运营')
platform_name = models.CharField(max_length=20, choices=PLATFORM_CHOICES, verbose_name='平台名称')
account_name = models.CharField(max_length=100, verbose_name='账号名称')
account_id = models.CharField(max_length=100, verbose_name='账号ID')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name='账号状态')
followers_count = models.IntegerField(default=0, verbose_name='粉丝数')
account_url = models.URLField(blank=True, null=True, verbose_name='账号链接')
description = models.TextField(blank=True, null=True, verbose_name='账号描述')
# 新增字段
tags = models.CharField(max_length=255, blank=True, null=True, verbose_name='标签', help_text='用逗号分隔的标签列表')
profile_image = models.URLField(blank=True, null=True, verbose_name='账号头像')
last_posting = models.DateTimeField(blank=True, null=True, verbose_name='最后发布时间')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间')
class Meta:
verbose_name = '平台账号'
verbose_name_plural = '平台账号'
unique_together = ('platform_name', 'account_id')
def __str__(self):
return f"{self.account_name} ({self.platform_name})"
class Video(models.Model):
"""视频信息表"""
STATUS_CHOICES = [
('draft', '草稿'),
('scheduled', '已排期'),
('published', '已发布'),
('failed', '发布失败'),
('deleted', '已删除'),
]
platform_account = models.ForeignKey(PlatformAccount, on_delete=models.CASCADE, related_name='videos', verbose_name='发布账号')
title = models.CharField(max_length=200, verbose_name='视频标题')
description = models.TextField(blank=True, null=True, verbose_name='视频描述')
video_url = models.URLField(blank=True, null=True, verbose_name='视频地址')
local_path = models.CharField(max_length=255, blank=True, null=True, verbose_name='本地路径')
thumbnail_url = models.URLField(blank=True, null=True, verbose_name='缩略图地址')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name='发布状态')
views_count = models.IntegerField(default=0, verbose_name='播放次数')
likes_count = models.IntegerField(default=0, verbose_name='点赞数')
comments_count = models.IntegerField(default=0, verbose_name='评论数')
shares_count = models.IntegerField(default=0, verbose_name='分享数')
tags = models.CharField(max_length=500, blank=True, null=True, verbose_name='标签')
publish_time = models.DateTimeField(blank=True, null=True, verbose_name='发布时间')
scheduled_time = models.DateTimeField(blank=True, null=True, verbose_name='计划发布时间')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '视频'
verbose_name_plural = '视频'
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if self.status == 'published' and not self.publish_time:
self.publish_time = timezone.now()
super().save(*args, **kwargs)
class GmailNotificationQueue(models.Model):
"""Gmail通知队列存储因认证失败等原因未能处理的通知"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_notification_queue')
gmail_credential = models.ForeignKey(GmailCredential, on_delete=models.CASCADE, related_name='notification_queue')
email = models.EmailField(verbose_name='邮箱', max_length=255)
history_id = models.CharField(verbose_name='历史ID', max_length=100)
notification_data = models.TextField(verbose_name='通知数据', blank=True, null=True)
processed = models.BooleanField(verbose_name='是否已处理', default=False)
success = models.BooleanField(verbose_name='处理是否成功', default=False)
error_message = models.CharField(verbose_name='错误信息', max_length=255, blank=True, null=True)
created_at = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
processed_at = models.DateTimeField(verbose_name='处理时间', blank=True, null=True)
def __str__(self):
return f"通知 {self.id} - {self.email} - {self.created_at}"
class Meta:
verbose_name = 'Gmail通知队列'
verbose_name_plural = 'Gmail通知队列'
ordering = ['processed', 'created_at']