完善权限

This commit is contained in:
wanjia 2025-03-29 12:26:50 +08:00
commit a90d31c420
19 changed files with 920 additions and 187 deletions

BIN
data_backup.json Normal file

Binary file not shown.

0
feishu/__init__.py Normal file
View File

184
feishu/feishu.py Normal file
View File

@ -0,0 +1,184 @@
import json
import django
import os
import sys
# 设置 Django 环境
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'role_based_system.settings')
django.setup()
import lark_oapi as lark
from lark_oapi.api.bitable.v1 import *
from user_management.models import FeishuCreator
# SDK 使用说明: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/preparations-before-development
# 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用
def extract_field_value(field_value):
"""提取字段值"""
if isinstance(field_value, list):
if field_value and isinstance(field_value[0], dict):
return field_value[0].get('text', '')
elif isinstance(field_value, dict):
if 'text' in field_value:
return field_value['text']
elif 'link' in field_value:
return field_value['link']
elif 'link_record_ids' in field_value:
return ''
return field_value
def save_to_database(record):
"""保存记录到数据库"""
fields = record.fields
record_id = record.record_id
creator_data = {
'record_id': record_id,
'contact_person': extract_field_value(fields.get('对接人', '')),
'handle': extract_field_value(fields.get('Handle', '')),
'tiktok_url': extract_field_value(fields.get('链接', '')),
'fans_count': extract_field_value(fields.get('粉丝数', '')),
'gmv': fields.get('GMV', ''),
'email': extract_field_value(fields.get('邮箱', '')),
'phone': extract_field_value(fields.get('手机号|WhatsApp', '')),
'account_type': extract_field_value(fields.get('账号属性', [])),
'price_quote': fields.get('报价', ''),
'response_speed': fields.get('回复速度', ''),
'cooperation_intention': fields.get('合作意向', ''),
'payment_method': fields.get('支付方式', ''),
'payment_account': fields.get('收款账号', ''),
'address': fields.get('收件地址', ''),
'has_ooin': fields.get('签约OOIN?', ''),
'source': fields.get('渠道来源', ''),
'contact_status': fields.get('建联进度', ''),
'cooperation_brands': fields.get('合作品牌', []),
'system_categories': extract_field_value(fields.get('系统展示的带货品类', [])),
'actual_categories': extract_field_value(fields.get('实际高播放量带货品类', [])),
'human_categories': fields.get('达人标想要货品类', ''),
'creator_base': '', # 如果有这个字段,添加相应的处理
'notes': extract_field_value(fields.get('父记录', '')),
}
try:
creator, created = FeishuCreator.objects.update_or_create(
record_id=record_id,
defaults=creator_data
)
return creator, created
except Exception as e:
print(f"保存记录时出错: {str(e)}")
print(f"记录数据: {creator_data}")
return None, False
def fetch_all_records(client, app_token, table_id, user_access_token):
"""获取所有记录"""
total_records = []
page_token = None
page_size = 20
while True:
try:
# 构造请求对象
builder = SearchAppTableRecordRequest.builder() \
.app_token(app_token) \
.table_id(table_id) \
.page_size(page_size)
# 如果有page_token添加到请求中
if page_token:
builder = builder.page_token(page_token)
# 构建完整请求
request = builder.request_body(SearchAppTableRecordRequestBody.builder().build()).build()
print(f"发送请求page_token: {page_token}")
# 发起请求
option = lark.RequestOption.builder().user_access_token(user_access_token).build()
response = client.bitable.v1.app_table_record.search(request, option)
if not response.success():
print(f"请求失败: {response.code}, {response.msg}")
break
# 获取当前页记录
current_records = response.data.items
if not current_records:
print("没有更多记录")
break
total_records.extend(current_records)
# 解析响应数据获取分页信息
response_data = json.loads(response.raw.content)
total = response_data["data"]["total"]
print(f"获取到 {len(current_records)} 条记录,当前总计: {len(total_records)}/{total}")
# 获取下一页token
page_token = response_data["data"].get("page_token")
if not page_token or not response_data["data"].get("has_more", False):
print("已获取所有数据")
break
except Exception as e:
print(f"错误: {str(e)}")
import traceback
print(traceback.format_exc())
break
print(f"最终获取到 {len(total_records)} 条记录")
return total_records
def main():
# 创建client
client = lark.Client.builder() \
.enable_set_token(True) \
.log_level(lark.LogLevel.DEBUG) \
.build()
# 配置参数
APP_TOKEN = "XYE6bMQUOaZ5y5svj4vcWohGnmg"
TABLE_ID = "tbl3oikG3F8YYtVA"
USER_ACCESS_TOKEN = "u-ecM5BmzKx4uHz3sG0FouQSk1l9kxgl_3Xa00l5Ma24Jy"
# 获取所有记录
print("开始获取所有记录...")
all_records = fetch_all_records(client, APP_TOKEN, TABLE_ID, USER_ACCESS_TOKEN)
if not all_records:
print("未获取到任何记录")
return
# 更新数据库
print("\n开始更新数据库...")
created_count = 0
updated_count = 0
error_count = 0
for record in all_records:
creator, created = save_to_database(record)
if creator:
if created:
created_count += 1
if created_count % 10 == 0: # 每10条才打印一次避免输出过多
print(f"已创建 {created_count} 条记录...")
else:
updated_count += 1
if updated_count % 10 == 0:
print(f"已更新 {updated_count} 条记录...")
else:
error_count += 1
print(f"处理记录失败")
# 打印统计信息
print("\n更新完成!统计信息:")
print(f"新建记录:{created_count}")
print(f"更新记录:{updated_count}")
print(f"错误记录:{error_count}")
print(f"总记录数:{len(all_records)}")
if __name__ == "__main__":
main()

1
gmail/client_secret.json Normal file
View File

@ -0,0 +1 @@
{"installed":{"client_id":"240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com","project_id":"first-renderer-454910-c1","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}

View File

@ -0,0 +1,20 @@
[
{
"subject": "这是主题",
"from": "crush wds <ardonisierni@gmail.com>",
"date": "2025-03-27 12:04:29",
"body": "你好呀\r\n"
},
{
"subject": "",
"from": "wds crush <crushwds@gmail.com>",
"date": "2025-03-27 12:05:54",
"body": "你那里天气怎么样\r\n"
},
{
"subject": "",
"from": "wds crush <crushwds@gmail.com>",
"date": "2025-03-27 13:13:03",
"body": "吃饭了吗\r\n"
}
]

View File

@ -0,0 +1,25 @@
==================================================
记录时间: 2025-03-27 13:15:18
==================================================
时间: 2025-03-27 12:04:29
发件人: crush wds <ardonisierni@gmail.com>
主题: 这是主题
内容:
你好呀
--------------------------------------------------
时间: 2025-03-27 12:05:54
发件人: wds crush <crushwds@gmail.com>
主题:
内容:
你那里天气怎么样
--------------------------------------------------
时间: 2025-03-27 13:13:03
发件人: wds crush <crushwds@gmail.com>
主题:
内容:
吃饭了吗
--------------------------------------------------

146
gmail/gmail_read.py Normal file
View File

@ -0,0 +1,146 @@
'''
Reading GMAIL using Python
- Abhishek Chhibber
'''
'''
This script does the following:
- Go to Gmal inbox
- Find and read all the unread messages
- Extract details (Date, Sender, Subject, Snippet, Body) and export them to a .csv file / DB
- Mark the messages as Read - so that they are not read again
'''
'''
Before running this script, the user should get the authentication by following
the link: https://developers.google.com/gmail/api/quickstart/python
Also, credentials.json should be saved in the same directory as this file
'''
# Importing required libraries
from apiclient import discovery
from apiclient import errors
from httplib2 import Http
from oauth2client import file, client, tools
import base64
from bs4 import BeautifulSoup
import re
import time
import dateutil.parser as parser
from datetime import datetime
import datetime
import csv
# Creating a storage.JSON file with authentication details
SCOPES = 'https://www.googleapis.com/auth/gmail.modify' # we are using modify and not readonly, as we will be marking the messages Read
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
user_id = 'me'
label_id_one = 'INBOX'
label_id_two = 'UNREAD'
# Getting all the unread messages from Inbox
# labelIds can be changed accordingly
unread_msgs = GMAIL.users().messages().list(userId='me',labelIds=[label_id_one, label_id_two]).execute()
# We get a dictonary. Now reading values for the key 'messages'
mssg_list = unread_msgs['messages']
print ("Total unread messages in inbox: ", str(len(mssg_list)))
final_list = [ ]
for mssg in mssg_list:
temp_dict = { }
m_id = mssg['id'] # get id of individual message
message = GMAIL.users().messages().get(userId=user_id, id=m_id).execute() # fetch the message using API
payld = message['payload'] # get payload of the message
headr = payld['headers'] # get header of the payload
for one in headr: # getting the Subject
if one['name'] == 'Subject':
msg_subject = one['value']
temp_dict['Subject'] = msg_subject
else:
pass
for two in headr: # getting the date
if two['name'] == 'Date':
msg_date = two['value']
date_parse = (parser.parse(msg_date))
m_date = (date_parse.date())
temp_dict['Date'] = str(m_date)
else:
pass
for three in headr: # getting the Sender
if three['name'] == 'From':
msg_from = three['value']
temp_dict['Sender'] = msg_from
else:
pass
temp_dict['Snippet'] = message['snippet'] # fetching message snippet
try:
# Fetching message body
mssg_parts = payld['parts'] # fetching the message parts
part_one = mssg_parts[0] # fetching first element of the part
part_body = part_one['body'] # fetching body of the message
part_data = part_body['data'] # fetching data from the body
clean_one = part_data.replace("-","+") # decoding from Base64 to UTF-8
clean_one = clean_one.replace("_","/") # decoding from Base64 to UTF-8
clean_two = base64.b64decode (bytes(clean_one, 'UTF-8')) # decoding from Base64 to UTF-8
soup = BeautifulSoup(clean_two , "lxml" )
mssg_body = soup.body()
# mssg_body is a readible form of message body
# depending on the end user's requirements, it can be further cleaned
# using regex, beautiful soup, or any other method
temp_dict['Message_body'] = mssg_body
except :
pass
print (temp_dict)
final_list.append(temp_dict) # This will create a dictonary item in the final list
# This will mark the messagea as read
GMAIL.users().messages().modify(userId=user_id, id=m_id,body={ 'removeLabelIds': ['UNREAD']}).execute()
print ("Total messaged retrived: ", str(len(final_list)))
'''
The final_list will have dictionary in the following format:
{ 'Sender': '"email.com" <name@email.com>',
'Subject': 'Lorem ipsum dolor sit ametLorem ipsum dolor sit amet',
'Date': 'yyyy-mm-dd',
'Snippet': 'Lorem ipsum dolor sit amet'
'Message_body': 'Lorem ipsum dolor sit amet'}
The dictionary can be exported as a .csv or into a databse
'''
#exporting the values as .csv
with open('CSV_NAME.csv', 'w', encoding='utf-8', newline = '') as csvfile:
fieldnames = ['Sender','Subject','Date','Snippet','Message_body']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter = ',')
writer.writeheader()
for val in final_list:
writer.writerow(val)

171
gmail/quickstart.py Normal file
View File

@ -0,0 +1,171 @@
from apiclient import discovery
from httplib2 import Http
from oauth2client import file, client, tools
import base64
from bs4 import BeautifulSoup
import dateutil.parser as parser
from datetime import datetime
import os
import json
# 代理设置
os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
# Gmail API 认证
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store)
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
def get_email_content(message):
"""提取邮件内容"""
try:
payload = message['payload']
headers = payload['headers']
# 获取邮件基本信息
email_data = {
'subject': '',
'from': '',
'date': '',
'body': ''
}
# 提取头部信息
for header in headers:
if header['name'] == 'Subject':
email_data['subject'] = header['value']
elif header['name'] == 'From':
email_data['from'] = header['value']
elif header['name'] == 'Date':
date = parser.parse(header['value'])
email_data['date'] = date.strftime('%Y-%m-%d %H:%M:%S')
# 提取邮件正文
if 'parts' in payload:
parts = payload['parts']
for part in parts:
if part['mimeType'] == 'text/plain':
data = part['body'].get('data', '')
if data:
text = base64.urlsafe_b64decode(data).decode('utf-8')
email_data['body'] = text
break
elif 'body' in payload:
data = payload['body'].get('data', '')
if data:
text = base64.urlsafe_b64decode(data).decode('utf-8')
email_data['body'] = text
return email_data
except Exception as e:
print(f"Error processing email: {str(e)}")
return None
def get_conversations(email1, email2):
"""获取两个用户之间的所有对话"""
try:
# 构建搜索查询
query = f"from:({email1} OR {email2}) to:({email1} OR {email2})"
# 获取所有匹配的邮件
response = GMAIL.users().messages().list(userId='me', q=query).execute()
messages = []
if 'messages' in response:
messages.extend(response['messages'])
# 如果有更多页,继续获取
while 'nextPageToken' in response:
page_token = response['nextPageToken']
response = GMAIL.users().messages().list(
userId='me',
q=query,
pageToken=page_token
).execute()
messages.extend(response['messages'])
# 获取每封邮件的详细内容
conversations = []
for msg in messages:
message = GMAIL.users().messages().get(userId='me', id=msg['id']).execute()
email_data = get_email_content(message)
if email_data:
conversations.append(email_data)
# 按时间排序
conversations.sort(key=lambda x: x['date'])
return conversations
except Exception as e:
print(f"Error getting conversations: {str(e)}")
return []
def save_conversations(conversations, output_file):
"""保存对话记录(覆盖模式)"""
try:
# 使用 'w' 模式覆盖内容
with open(output_file, 'w', encoding='utf-8') as f:
# 写入时间分割线
f.write("=" * 50 + "\n")
f.write(f"记录时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("=" * 50 + "\n\n")
# 写入对话记录
for msg in conversations:
f.write(f"时间: {msg['date']}\n")
f.write(f"发件人: {msg['from']}\n")
f.write(f"主题: {msg['subject']}\n")
f.write("内容:\n")
f.write(f"{msg['body']}\n")
f.write("-" * 50 + "\n")
print(f"对话记录已保存到: {output_file}")
# 保存 JSON 格式
json_file = output_file.rsplit('.', 1)[0] + '.json'
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(conversations, f, ensure_ascii=False, indent=2)
print(f"JSON 格式对话记录已保存到: {json_file}")
except Exception as e:
print(f"Error saving conversations: {str(e)}")
def main():
# 设置固定的输出文件名,这样每次都会追加到同一个文件
output_file = "email_conversations.txt"
# 设置要查找的两个邮箱地址
email1 = "crushwds@gmail.com"
email2 = "ardonisierni@gmail.com"
print(f"正在获取 {email1}{email2} 之间的对话...")
# 获取对话记录
conversations = get_conversations(email1, email2)
if conversations:
print(f"找到 {len(conversations)} 条对话记录")
# 保存对话记录(追加模式)
save_conversations(conversations, output_file)
# 打印对话统计
print("\n对话统计:")
print(f"总消息数: {len(conversations)}")
senders = {}
for msg in conversations:
sender = msg['from']
senders[sender] = senders.get(sender, 0) + 1
for sender, count in senders.items():
print(f"{sender}: {count} 条消息")
else:
print("未找到对话记录")
if __name__ == "__main__":
main()

3
gmail/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
google-api-python-client==1.7.8
google-auth-httplib2==0.0.3
google-auth-oauthlib==0.4.0

1
gmail/storage.json Normal file
View File

@ -0,0 +1 @@
{"access_token": "ya29.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eUNnePCc4hEKCgYIARAAGA4SNwF-L9IrIRfxg1JwnMu0WHZAu-uCWT7cx1PncId3bPZr4x53Mo57xGXr2WjiF4szuSQQkeL4hNU", "token_expiry": "2025-03-27T06:13:12Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "expires_in": 3599, "scope": "https://www.googleapis.com/auth/gmail.modify", "token_type": "Bearer", "refresh_token_expires_in": 599654}, "scopes": ["https://www.googleapis.com/auth/gmail.modify"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}

1
gmail/test.txt Normal file
View File

@ -0,0 +1 @@
你好

1
gmail/token.json Normal file
View File

@ -0,0 +1 @@
{"token": "ya29.a0AeXRPp5MvarP9U4JZ3ZlV_xpIeHNL7IK1HEQLX6qhY-thS5IOFsdHrcNwbdNqkYrClFm4yGiMCXzWO-IilVbdSrDfEDw-Qfs7V9oTsZS2RYjbgcSR9rLUtZlL8ObnaljQie5VVzQZysHPZcMSdWiqabcQq2UsU5TNuOWo4J7_waCgYKAQESARISFQHGX2MiAR1O_HNYd0F6z0Rj-vSybA0177", "refresh_token": "1//0e1Usc30XxlXRCgYIARAAGA4SNwF-L9IrOOjEN7EkOSatDMHzTy0KaNdp_jf1XtLn7a5pFSljo3IKnrDdvVnxTO_FX5Asj_UDiAw", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "scopes": ["https://www.googleapis.com/auth/gmail.readonly"], "universe_domain": "googleapis.com", "account": "", "expiry": "2025-03-27T04:39:31.274041Z"}

View File

@ -106,7 +106,15 @@ DATABASES = {
'PASSWORD': '123456', 'PASSWORD': '123456',
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '3306', 'PORT': '3306',
} 'OPTIONS': {
'charset': 'utf8mb4', # 使用 utf8mb4 字符集
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'; SET innodb_strict_mode=1; SET NAMES utf8mb4;",
},
'TEST': {
'CHARSET': 'utf8mb4',
'COLLATION': 'utf8mb4_unicode_ci',
},
}
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.5 on 2025-02-26 09:23 # Generated by Django 5.1.5 on 2025-03-28 06:51
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators
@ -18,6 +18,43 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='FeishuCreator',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('record_id', models.CharField(max_length=100, unique=True, verbose_name='飞书记录ID')),
('contact_person', models.CharField(blank=True, max_length=50, verbose_name='对接人')),
('handle', models.CharField(blank=True, max_length=100, verbose_name='Handle')),
('tiktok_url', models.URLField(blank=True, verbose_name='链接')),
('fans_count', models.CharField(blank=True, max_length=50, verbose_name='粉丝数')),
('gmv', models.CharField(blank=True, max_length=50, verbose_name='GMV')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='邮箱')),
('phone', models.CharField(blank=True, max_length=50, verbose_name='手机号|WhatsApp')),
('account_type', models.CharField(blank=True, max_length=50, verbose_name='账号属性')),
('price_quote', models.CharField(blank=True, max_length=100, verbose_name='报价')),
('response_speed', models.CharField(blank=True, max_length=50, verbose_name='回复速度')),
('cooperation_intention', models.CharField(blank=True, max_length=50, verbose_name='合作意向')),
('payment_method', models.CharField(blank=True, max_length=50, verbose_name='支付方式')),
('payment_account', models.CharField(blank=True, max_length=100, verbose_name='收款账号')),
('address', models.TextField(blank=True, verbose_name='收件地址')),
('has_ooin', models.CharField(blank=True, max_length=10, verbose_name='签约OOIN?')),
('source', models.CharField(blank=True, max_length=100, verbose_name='渠道来源')),
('contact_status', models.CharField(blank=True, max_length=50, verbose_name='建联进度')),
('cooperation_brands', models.JSONField(blank=True, default=list, verbose_name='合作品牌')),
('system_categories', models.CharField(blank=True, max_length=100, verbose_name='系统展示的带货品类')),
('actual_categories', models.CharField(blank=True, max_length=100, verbose_name='实际高播放量带货品类')),
('human_categories', models.CharField(blank=True, max_length=100, verbose_name='达人标想要货品类')),
('creator_base', models.CharField(blank=True, max_length=100, 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='更新时间')),
],
options={
'verbose_name': '创作者数据',
'verbose_name_plural': '创作者数据',
'db_table': 'feishu_creators',
},
),
migrations.CreateModel( migrations.CreateModel(
name='User', name='User',
fields=[ fields=[
@ -73,79 +110,22 @@ class Migration(migrations.Migration):
name='KnowledgeBase', name='KnowledgeBase',
fields=[ fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, verbose_name='知识库名称')), ('user_id', models.UUIDField(verbose_name='创建者ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='知识库名称')),
('desc', models.TextField(blank=True, null=True, verbose_name='知识库描述')), ('desc', models.TextField(blank=True, null=True, verbose_name='知识库描述')),
('type', models.CharField(choices=[('admin', '管理级知识库'), ('leader', '部门级知识库'), ('member', '成员级知识库'), ('private', '私有知识库'), ('secret', '公司级别私密知识库')], default='private', max_length=20, verbose_name='知识库类型')), ('type', models.CharField(choices=[('admin', '管理级知识库'), ('leader', '部门级知识库'), ('member', '成员级知识库'), ('private', '私有知识库'), ('secret', '公司级别私密知识库')], default='private', max_length=20, verbose_name='知识库类型')),
('department', models.CharField(blank=True, max_length=50, null=True)), ('department', models.CharField(blank=True, max_length=50, null=True)),
('group', models.CharField(blank=True, max_length=50, null=True)), ('group', models.CharField(blank=True, max_length=50, null=True)),
('user_id', models.CharField(max_length=50, verbose_name='创建者ID')),
('documents', models.JSONField(default=list)), ('documents', models.JSONField(default=list)),
('char_length', models.IntegerField(default=0)), ('char_length', models.IntegerField(default=0)),
('document_count', models.IntegerField(default=0)), ('document_count', models.IntegerField(default=0)),
('external_id', models.UUIDField(blank=True, null=True)), ('external_id', models.UUIDField(blank=True, null=True)),
('create_time', models.DateTimeField(auto_now_add=True)), ('create_time', models.DateTimeField(auto_now_add=True)),
('update_time', models.DateTimeField(auto_now=True)), ('update_time', models.DateTimeField(auto_now=True)),
('owners', models.ManyToManyField(related_name='owned_knowledge_bases', to=settings.AUTH_USER_MODEL, verbose_name='所有者')),
], ],
options={ options={
'db_table': 'knowledge_bases', 'db_table': 'knowledge_bases',
}, 'indexes': [models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'), models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'), models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx')],
),
migrations.CreateModel(
name='ChatHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('conversation_id', models.CharField(db_index=True, max_length=100)),
('parent_id', models.CharField(blank=True, max_length=100, null=True)),
('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)),
('content', models.TextField()),
('tokens', models.IntegerField(default=0, help_text='消息token数')),
('metadata', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')),
],
options={
'db_table': 'chat_history',
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='KnowledgeBasePermission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')),
('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')),
('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')),
('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')),
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
],
options={
'verbose_name': '知识库权限',
'verbose_name_plural': '知识库权限',
'db_table': 'knowledge_base_permissions',
},
),
migrations.CreateModel(
name='Notification',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)),
('title', models.CharField(max_length=100)),
('content', models.TextField()),
('is_read', models.BooleanField(default=False)),
('related_resource', models.CharField(blank=True, max_length=100)),
('created_at', models.DateTimeField(auto_now_add=True)),
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)),
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
@ -181,40 +161,65 @@ class Migration(migrations.Migration):
'db_table': 'user_profiles', 'db_table': 'user_profiles',
}, },
), ),
migrations.AddIndex( migrations.CreateModel(
model_name='knowledgebase', name='ChatHistory',
index=models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'), fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('conversation_id', models.CharField(db_index=True, max_length=100)),
('parent_id', models.CharField(blank=True, max_length=100, null=True)),
('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)),
('content', models.TextField()),
('tokens', models.IntegerField(default=0, help_text='消息token数')),
('metadata', models.JSONField(blank=True, default=dict, help_text="\n {\n 'model_id': 'xxx',\n 'dataset_id_list': ['id1', 'id2', ...],\n 'dataset_external_id_list': ['ext1', 'ext2', ...],\n 'primary_knowledge_base': 'id1'\n }\n ")),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')),
],
options={
'db_table': 'chat_history',
'ordering': ['created_at'],
'indexes': [models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'), models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx'), models.Index(fields=['conversation_id', 'is_deleted'], name='chat_histor_convers_89bc43_idx')],
},
), ),
migrations.AddIndex( migrations.CreateModel(
model_name='knowledgebase', name='KnowledgeBasePermission',
index=models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'), fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')),
('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')),
('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')),
('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')),
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
],
options={
'verbose_name': '知识库权限',
'verbose_name_plural': '知识库权限',
'db_table': 'knowledge_base_permissions',
'indexes': [models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx')],
'unique_together': {('knowledge_base', 'user')},
},
), ),
migrations.AddIndex( migrations.CreateModel(
model_name='knowledgebase', name='Notification',
index=models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx'), fields=[
), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
migrations.AddIndex( ('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)),
model_name='chathistory', ('title', models.CharField(max_length=100)),
index=models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'), ('content', models.TextField()),
), ('is_read', models.BooleanField(default=False)),
migrations.AddIndex( ('related_resource', models.CharField(blank=True, max_length=100)),
model_name='chathistory', ('created_at', models.DateTimeField(auto_now_add=True)),
index=models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx'), ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)),
), ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)),
migrations.AddIndex( ],
model_name='knowledgebasepermission', options={
index=models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx'), 'ordering': ['-created_at'],
), 'indexes': [models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'), models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx')],
migrations.AlterUniqueTogether( },
name='knowledgebasepermission',
unique_together={('knowledge_base', 'user')},
),
migrations.AddIndex(
model_name='notification',
index=models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'),
),
migrations.AddIndex(
model_name='notification',
index=models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx'),
), ),
] ]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.1.5 on 2025-03-28 07:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='feishucreator',
name='gmv',
field=models.CharField(blank=True, max_length=100, verbose_name='GMV'),
),
migrations.AlterField(
model_name='feishucreator',
name='price_quote',
field=models.TextField(blank=True, verbose_name='报价'),
),
migrations.AlterField(
model_name='feishucreator',
name='tiktok_url',
field=models.TextField(blank=True, verbose_name='链接'),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-26 02:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='knowledgebase',
name='owners',
),
migrations.AlterField(
model_name='knowledgebase',
name='name',
field=models.CharField(max_length=100, unique=True, verbose_name='知识库名称'),
),
migrations.AlterField(
model_name='knowledgebase',
name='user_id',
field=models.UUIDField(verbose_name='创建者ID'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-03-28 07:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0002_alter_feishucreator_gmv_and_more'),
]
operations = [
migrations.AlterField(
model_name='feishucreator',
name='handle',
field=models.TextField(blank=True, verbose_name='Handle'),
),
]

View File

@ -1,3 +1,4 @@
from itertools import count
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -286,13 +287,23 @@ class ChatHistory(models.Model):
] ]
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
# 保留与主知识库的关联
knowledge_base = models.ForeignKey('KnowledgeBase', on_delete=models.CASCADE) knowledge_base = models.ForeignKey('KnowledgeBase', on_delete=models.CASCADE)
# 用于标识知识库组合的对话
conversation_id = models.CharField(max_length=100, db_index=True) conversation_id = models.CharField(max_length=100, db_index=True)
parent_id = models.CharField(max_length=100, null=True, blank=True) parent_id = models.CharField(max_length=100, null=True, blank=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES) role = models.CharField(max_length=20, choices=ROLE_CHOICES)
content = models.TextField() content = models.TextField()
tokens = models.IntegerField(default=0, help_text="消息token数") tokens = models.IntegerField(default=0, help_text="消息token数")
metadata = models.JSONField(default=dict, blank=True) # 扩展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) created_at = models.DateTimeField(auto_now_add=True)
is_deleted = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False)
@ -302,6 +313,8 @@ class ChatHistory(models.Model):
indexes = [ indexes = [
models.Index(fields=['conversation_id', 'created_at']), models.Index(fields=['conversation_id', 'created_at']),
models.Index(fields=['user', 'created_at']), models.Index(fields=['user', 'created_at']),
# 添加新的索引以支持知识库组合查询
models.Index(fields=['conversation_id', 'is_deleted']),
] ]
def __str__(self): def __str__(self):
@ -315,11 +328,69 @@ class ChatHistory(models.Model):
is_deleted=False is_deleted=False
).order_by('created_at') ).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): def soft_delete(self):
"""软删除消息""" """软删除消息"""
self.is_deleted = True self.is_deleted = True
self.save() 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): class UserProfile(models.Model):
"""用户档案模型""" """用户档案模型"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
@ -547,3 +618,54 @@ class KnowledgeBase(models.Model):
embedding_mode_id=embedding_mode_id, embedding_mode_id=embedding_mode_id,
document_count=len(data["documents"]) if data.get("documents") else 0 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 = '创作者数据'

View File

@ -1091,46 +1091,51 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
"""获取用户有权限查看的知识库列表""" """获取用户有权限查看的知识库列表"""
user = self.request.user user = self.request.user
all_knowledge_bases = KnowledgeBase.objects.all() queryset = KnowledgeBase.objects.all()
# 首先过滤出用户创建的或有显式读权限的知识库 # 1. 构建基础权限条件
explicit_permission_kbs = KnowledgeBase.objects.filter( permission_conditions = Q()
Q(user_id=user.id) | # 是创建者
Q( # 或在权限表中有读权限 # 2. 所有用户都可以看到 admin 类型的知识库
id__in=KBPermissionModel.objects.filter( permission_conditions |= Q(type='admin')
user=user,
can_read=True, # 3. 用户可以看到自己创建的所有知识库
status='active' permission_conditions |= Q(user_id=user.id)
).values_list('knowledge_base_id', flat=True)
) # 4. 添加显式权限条件
permission_conditions |= Q(
id__in=KBPermissionModel.objects.filter(
user=user,
can_read=True,
status='active',
expires_at__gt=timezone.now()
).values_list('knowledge_base_id', flat=True)
) )
# 获取显式权限知识库的ID集合 # 5. 根据用户角色添加隐式权限
explicit_kb_ids = set(explicit_permission_kbs.values_list('id', flat=True))
# 对于没有显式权限的知识库使用_can_read方法判断
result_kbs = list(explicit_permission_kbs)
# 如果用户是管理员,可以查看所有知识库
if user.role == 'admin': if user.role == 'admin':
return all_knowledge_bases # 管理员可以看到除了其他用户 private 类型外的所有知识库
permission_conditions |= ~Q(type='private') | Q(user_id=user.id)
elif user.role == 'leader':
# 组长可以查看本部门的 leader 和 member 类型知识库
permission_conditions |= Q(
type__in=['leader', 'member'],
department=user.department
)
elif user.role in ['member', 'user']:
# 成员可以查看本部门的 leader 类型知识库
permission_conditions |= Q(
type='leader',
department=user.department
)
# 成员可以查看本部门本组的 member 类型知识库
permission_conditions |= Q(
type='member',
department=user.department,
group=user.group
)
# 否则,根据角色和部门/组筛选 return queryset.filter(permission_conditions).distinct()
for kb in all_knowledge_bases:
if kb.id not in explicit_kb_ids:
has_read_permission = self._can_read(
type=kb.type,
user=user,
department=kb.department,
group=kb.group,
creator_id=kb.user_id
)
if has_read_permission:
result_kbs.append(kb)
# 转换为queryset并去重
kb_ids = [kb.id for kb in result_kbs]
return KnowledgeBase.objects.filter(id__in=kb_ids).distinct()
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
try: try:
@ -1338,74 +1343,95 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
def _can_edit(self, type, user, department=None, group=None, creator_id=None): def _can_edit(self, type, user, department=None, group=None, creator_id=None):
"""判断用户是否有编辑权限""" """判断用户是否有编辑权限"""
# admin 类型知识库所有用户都有编辑权限
if type == 'admin':
return True
if user.role == 'admin': if user.role == 'admin':
return True # 管理员对所有知识库都有编辑权限 # 管理员对其他用户的 private 类型没有编辑权限
if type == 'private' and str(user.id) != str(creator_id):
return False
return True
if type == 'secret': if type == 'secret':
return False # 除管理员外其他人无权编辑secret类型知识库 return False # 除管理员外,其他人无权编辑 secret 类型知识库
if type == 'leader': if type == 'leader':
# 同部门组长可编辑leader知识库 # 同部门组长可编辑 leader 知识库
return user.role == 'leader' and user.department == department return user.role == 'leader' and user.department == department
if type == 'member': if type == 'member':
# 同部门组长可编辑所有member知识库 # 同部门组长可编辑所有 member 知识库
if user.role == 'leader' and user.department == department: if user.role == 'leader' and user.department == department:
return True return True
# 成员只能编辑同部门同组的member知识库 # 成员只能编辑同部门同组的 member 知识库
return user.role == 'member' and user.department == department and user.group == group return user.role == 'member' and user.department == department and user.group == group
if type == 'private': if type == 'private':
# 私有知识库只有创建者可编辑 # 私有知识库只有创建者可编辑
return str(user.id) == str(creator_id) return str(user.id) == str(creator_id)
return False return False
def _can_delete(self, type, user, department=None, group=None, creator_id=None): def _can_delete(self, type, user, department=None, group=None, creator_id=None):
"""判断用户是否有删除权限""" """判断用户是否有删除权限"""
# admin 类型知识库所有用户都有删除权限
if type == 'admin':
return True
if user.role == 'admin': if user.role == 'admin':
return True # 管理员对所有知识库都有删除权限 # 管理员对其他用户的 private 类型没有删除权限
if type == 'private' and str(user.id) != str(creator_id):
if type in ['admin', 'secret']: return False
return False # 除管理员外其他人无权删除admin和secret类型知识库 return True
if type == 'secret':
return False # 除管理员外,其他人无权删除 admin 和 secret 类型知识库
if type == 'leader': if type == 'leader':
# 同部门组长可删除leader知识库 # 同部门组长可删除 leader 知识库
return user.role == 'leader' and user.department == department return user.role == 'leader' and user.department == department
if type == 'member': if type == 'member':
# 只有组长可以删除成员知识库(组员不能删除) # 只有组长可以删除成员知识库(组员不能删除)
return user.role == 'leader' and user.department == department return user.role == 'leader' and user.department == department
if type == 'private': if type == 'private':
# 私有知识库只有创建者可删除 # 私有知识库只有创建者可删除
return str(user.id) == str(creator_id) return str(user.id) == str(creator_id)
return False return False
def _can_read(self, type, user, department=None, group=None, creator_id=None): def _can_read(self, type, user, department=None, group=None, creator_id=None):
"""判断用户是否有读取权限""" """判断用户是否有读取权限"""
# admin 类型知识库所有用户都有读取权限
if type == 'admin':
return True
if user.role == 'admin': if user.role == 'admin':
return True # 管理员可以读取所有知识库 # 管理员对其他用户的 private 类型没有读取权限
if type == 'private' and str(user.id) != str(creator_id):
return False
return True
if type == 'secret': if type == 'secret':
return False # 除管理员外secret类型知识库对其他人不可读 return False # 除管理员外secret 类型知识库对其他人不可读
if type == 'leader': if type == 'leader':
# 同部门的leader和member都可以读取 # 同部门的 leader member 都可以读取
return user.department == department return user.department == department
if type == 'member': if type == 'member':
# 同部门组长可以读取所有member知识库 # 同部门组长可以读取所有 member 知识库
if user.role == 'leader' and user.department == department: if user.role == 'leader' and user.department == department:
return True return True
# 成员可以读取同部门同组的member知识库 # 成员可以读取同部门同组的 member 知识库
return user.role == 'member' and user.department == department and user.group == group return user.role == 'member' and user.department == department and user.group == group
if type == 'private': if type == 'private':
# 私有知识库只有创建者可读 # 私有知识库只有创建者可读
return str(user.id) == str(creator_id) return str(user.id) == str(creator_id)
return False return False
def _create_external_dataset(self, instance): def _create_external_dataset(self, instance):