标签趋势自然语言进行搜索

This commit is contained in:
wanjia 2025-05-23 17:18:51 +08:00
parent 4a2e7f1222
commit 5b9b6da042
7 changed files with 1021 additions and 503 deletions

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-05-23 08:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('daren_detail', '0002_remove_creatorprofile_pricing_max_and_more'),
]
operations = [
migrations.AddField(
model_name='creatorprofile',
name='hashtags',
field=models.TextField(blank=True, help_text='以#分隔的标签,例如:#fashion#beauty#lifestyle', null=True, verbose_name='标签'),
),
migrations.AddField(
model_name='creatorprofile',
name='trends',
field=models.TextField(blank=True, help_text='创作者相关的趋势关键词', null=True, verbose_name='趋势'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-05-23 09:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('daren_detail', '0003_creatorprofile_hashtags_creatorprofile_trends'),
]
operations = [
migrations.AddField(
model_name='creatorprofile',
name='profile',
field=models.CharField(choices=[('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('youtube', 'YouTube'), ('xiaohongshu', '小红书'), ('other', '其他平台')], default='tiktok', max_length=20, verbose_name='达人平台'),
),
]

View File

@ -1,483 +1,497 @@
from django.db import models from django.db import models
# from apps.daren_detail.models import User # from apps.daren_detail.models import User
# 修改User模型导入 # 修改User模型导入
from apps.user.models import User from apps.user.models import User
from apps.brands.models import Campaign as BrandCampaign from apps.brands.models import Campaign as BrandCampaign
# 新增模型:协作指标数据 # 新增模型:协作指标数据
class CollaborationMetrics(models.Model): class CollaborationMetrics(models.Model):
"""协作指标数据模型Collaboration Metrics部分""" """协作指标数据模型Collaboration Metrics部分"""
avg_commission_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均佣金率(%)") avg_commission_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均佣金率(%)")
products_count = models.IntegerField(verbose_name="产品数量") products_count = models.IntegerField(verbose_name="产品数量")
brand_collaborations = models.IntegerField(verbose_name="品牌合作数量") brand_collaborations = models.IntegerField(verbose_name="品牌合作数量")
min_product_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="最低产品价格($)") min_product_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="最低产品价格($)")
max_product_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="最高产品价格($)") max_product_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="最高产品价格($)")
# 时间范围 # 时间范围
start_date = models.DateField(verbose_name="开始日期") start_date = models.DateField(verbose_name="开始日期")
end_date = models.DateField(verbose_name="结束日期") end_date = models.DateField(verbose_name="结束日期")
# 所属创作者 # 所属创作者
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="collaboration_metrics", creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="collaboration_metrics",
verbose_name="创作者") verbose_name="创作者")
# 时间戳 # 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta: class Meta:
verbose_name = "协作指标" verbose_name = "协作指标"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
db_table = "collaboration_metrics" db_table = "collaboration_metrics"
def __str__(self): def __str__(self):
return f"{self.creator.name}的协作指标 ({self.start_date} - {self.end_date})" return f"{self.creator.name}的协作指标 ({self.start_date} - {self.end_date})"
# 新增模型:视频指标数据 # 新增模型:视频指标数据
class VideoMetrics(models.Model): class VideoMetrics(models.Model):
"""视频指标数据模型Video和Shoppable Video部分""" """视频指标数据模型Video和Shoppable Video部分"""
video_type = models.CharField(max_length=50, choices=[ video_type = models.CharField(max_length=50, choices=[
('regular', '普通视频'), ('regular', '普通视频'),
('shoppable', '可购物视频') ('shoppable', '可购物视频')
], default='regular', verbose_name="视频类型") ], default='regular', verbose_name="视频类型")
gpm = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="视频GPM($)") gpm = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="视频GPM($)")
videos_count = models.IntegerField(verbose_name="视频数量") videos_count = models.IntegerField(verbose_name="视频数量")
avg_views = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="平均观看量") avg_views = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="平均观看量")
avg_engagement = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均互动率(%)") avg_engagement = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均互动率(%)")
avg_likes = models.IntegerField(verbose_name="平均点赞数") avg_likes = models.IntegerField(verbose_name="平均点赞数")
# 时间范围 # 时间范围
start_date = models.DateField(verbose_name="开始日期") start_date = models.DateField(verbose_name="开始日期")
end_date = models.DateField(verbose_name="结束日期") end_date = models.DateField(verbose_name="结束日期")
# 所属创作者 # 所属创作者
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="video_metrics", creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="video_metrics",
verbose_name="创作者") verbose_name="创作者")
# 时间戳 # 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta: class Meta:
verbose_name = "视频指标" verbose_name = "视频指标"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
db_table = "video_metrics" db_table = "video_metrics"
unique_together = ('creator', 'video_type', 'start_date', 'end_date') # 同一创作者同一时间段同一类型只能有一条记录 unique_together = ('creator', 'video_type', 'start_date', 'end_date') # 同一创作者同一时间段同一类型只能有一条记录
def __str__(self): def __str__(self):
return f"{self.creator.name}{self.get_video_type_display()}指标 ({self.start_date} - {self.end_date})" return f"{self.creator.name}{self.get_video_type_display()}指标 ({self.start_date} - {self.end_date})"
# 新增模型:直播指标数据 # 新增模型:直播指标数据
class LiveMetrics(models.Model): class LiveMetrics(models.Model):
"""直播指标数据模型LIVE和Shoppable LIVE部分""" """直播指标数据模型LIVE和Shoppable LIVE部分"""
live_type = models.CharField(max_length=50, choices=[ live_type = models.CharField(max_length=50, choices=[
('regular', '普通直播'), ('regular', '普通直播'),
('shoppable', '可购物直播') ('shoppable', '可购物直播')
], default='regular', verbose_name="直播类型") ], default='regular', verbose_name="直播类型")
gpm = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="直播GPM($)") gpm = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="直播GPM($)")
lives_count = models.IntegerField(verbose_name="直播数量") lives_count = models.IntegerField(verbose_name="直播数量")
avg_views = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="平均观看量") avg_views = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="平均观看量")
avg_engagement = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均互动率(%)") avg_engagement = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="平均互动率(%)")
avg_likes = models.IntegerField(verbose_name="平均点赞数") avg_likes = models.IntegerField(verbose_name="平均点赞数")
# 时间范围 # 时间范围
start_date = models.DateField(verbose_name="开始日期") start_date = models.DateField(verbose_name="开始日期")
end_date = models.DateField(verbose_name="结束日期") end_date = models.DateField(verbose_name="结束日期")
# 所属创作者 # 所属创作者
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="live_metrics", creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name="live_metrics",
verbose_name="创作者") verbose_name="创作者")
# 时间戳 # 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta: class Meta:
verbose_name = "直播指标" verbose_name = "直播指标"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
db_table = "live_metrics" db_table = "live_metrics"
unique_together = ('creator', 'live_type', 'start_date', 'end_date') # 同一创作者同一时间段同一类型只能有一条记录 unique_together = ('creator', 'live_type', 'start_date', 'end_date') # 同一创作者同一时间段同一类型只能有一条记录
def __str__(self): def __str__(self):
return f"{self.creator.name}{self.get_live_type_display()}指标 ({self.start_date} - {self.end_date})" return f"{self.creator.name}{self.get_live_type_display()}指标 ({self.start_date} - {self.end_date})"
class CreatorProfile(models.Model): class CreatorProfile(models.Model):
"""达人信息模型,用于筛选功能""" """达人信息模型,用于筛选功能"""
# 基本信息 # 基本信息
name = models.CharField(max_length=255, verbose_name="达人名称") name = models.CharField(max_length=255, verbose_name="达人名称")
avatar_url = models.TextField(blank=True, null=True, verbose_name="头像URL") avatar_url = models.TextField(blank=True, null=True, verbose_name="头像URL")
# 新增联系方式 # 新增联系方式
email = models.EmailField(max_length=255, blank=True, null=True, verbose_name="电子邮箱") email = models.EmailField(max_length=255, blank=True, null=True, verbose_name="电子邮箱")
instagram = models.CharField(max_length=255, blank=True, null=True, verbose_name="Instagram账号") instagram = models.CharField(max_length=255, blank=True, null=True, verbose_name="Instagram账号")
tiktok_link = models.URLField(max_length=255, blank=True, null=True, verbose_name="TikTok链接") tiktok_link = models.URLField(max_length=255, blank=True, null=True, verbose_name="TikTok链接")
location = models.CharField(max_length=100, blank=True, null=True, verbose_name="位置") location = models.CharField(max_length=100, blank=True, null=True, verbose_name="位置")
live_schedule = models.CharField(max_length=255, blank=True, null=True, verbose_name="直播时间表") live_schedule = models.CharField(max_length=255, blank=True, null=True, verbose_name="直播时间表")
# 类别 - Category # 新增达人平台字段
CATEGORY_CHOICES = [ PROFILE_CHOICES = [
('Phones & Electronics', '手机与电子产品'), ('tiktok', 'TikTok'),
('Homes Supplies', '家居用品'), ('instagram', 'Instagram'),
('Kitchenware', '厨房用品'), ('youtube', 'YouTube'),
('Textiles & Soft Furnishings', '纺织品和软装'), ('xiaohongshu', '小红书'),
('Household Appliances', '家用电器'), ('other', '其他平台'),
('Womenswear & Underwear', '女装和内衣'), ]
('Muslim Fashion', '穆斯林时尚'), profile = models.CharField(max_length=20, choices=PROFILE_CHOICES, default='tiktok', verbose_name="达人平台")
('Shoes', '鞋类'),
('Beauty & Personal Care', '美容和个人护理'), # 新增hashtag和trend字段
('Computers & Office Equipment', '电脑和办公设备'), hashtags = models.TextField(blank=True, null=True, verbose_name="标签", help_text="以#分隔的标签,例如:#fashion#beauty#lifestyle")
('Pet Supplies', '宠物用品'), trends = models.TextField(blank=True, null=True, verbose_name="趋势", help_text="创作者相关的趋势关键词")
('Baby & Maternity', '婴儿和孕妇用品'),
('Sports & Outdoor', '运动和户外'), # 类别 - Category
('Toys', '玩具'), CATEGORY_CHOICES = [
('Furniture', '家具'), ('Phones & Electronics', '手机与电子产品'),
('Tools & Hardware', '工具和硬件'), ('Homes Supplies', '家居用品'),
('Home Improvement', '家居装修'), ('Kitchenware', '厨房用品'),
('Automotive & Motorcycle', '汽车和摩托车'), ('Textiles & Soft Furnishings', '纺织品和软装'),
('Fashion Accessories', '时尚配饰'), ('Household Appliances', '家用电器'),
('Food & Beverages', '食品和饮料'), ('Womenswear & Underwear', '女装和内衣'),
('Health', '健康'), ('Muslim Fashion', '穆斯林时尚'),
('Books, Magazines & Audio', '书籍、杂志和音频'), ('Shoes', '鞋类'),
('Kids Fashion', '儿童时尚'), ('Beauty & Personal Care', '美容和个人护理'),
('Menswear & Underwear', '男装和内衣'), ('Computers & Office Equipment', '电脑和办公设备'),
('Luggage & Bags', '行李和包'), ('Pet Supplies', '宠物用品'),
('Pre-Owned Collections', '二手收藏'), ('Baby & Maternity', '婴儿和孕妇用品'),
('Jewellery Accessories & Derivatives', '珠宝配饰及衍生品'), ('Sports & Outdoor', '运动和户外'),
] ('Toys', '玩具'),
category = models.CharField(max_length=100, choices=CATEGORY_CHOICES, blank=True, null=True, verbose_name="类别") ('Furniture', '家具'),
('Tools & Hardware', '工具和硬件'),
# 电商等级 - E-commerce Level (L1-L7) ('Home Improvement', '家居装修'),
E_COMMERCE_LEVEL_CHOICES = [ ('Automotive & Motorcycle', '汽车和摩托车'),
(1, 'L1'), ('Fashion Accessories', '时尚配饰'),
(2, 'L2'), ('Food & Beverages', '食品和饮料'),
(3, 'L3'), ('Health', '健康'),
(4, 'L4'), ('Books, Magazines & Audio', '书籍、杂志和音频'),
(5, 'L5'), ('Kids Fashion', '儿童时尚'),
(6, 'L6'), ('Menswear & Underwear', '男装和内衣'),
(7, 'L7'), ('Luggage & Bags', '行李和包'),
] ('Pre-Owned Collections', '二手收藏'),
e_commerce_level = models.IntegerField(choices=E_COMMERCE_LEVEL_CHOICES, blank=True, null=True, ('Jewellery Accessories & Derivatives', '珠宝配饰及衍生品'),
verbose_name="电商能力等级") ]
category = models.CharField(max_length=100, choices=CATEGORY_CHOICES, blank=True, null=True, verbose_name="类别")
# 曝光等级 - Exposure Level (KOC-1, KOC-2, KOL-1, KOL-2, KOL-3)
EXPOSURE_LEVEL_CHOICES = [ # 电商等级 - E-commerce Level (L1-L7)
('KOC-1', 'KOC-1'), E_COMMERCE_LEVEL_CHOICES = [
('KOC-2', 'KOC-2'), (1, 'L1'),
('KOL-1', 'KOL-1'), (2, 'L2'),
('KOL-2', 'KOL-2'), (3, 'L3'),
('KOL-3', 'KOL-3'), (4, 'L4'),
] (5, 'L5'),
exposure_level = models.CharField(max_length=10, choices=EXPOSURE_LEVEL_CHOICES, blank=True, null=True, (6, 'L6'),
verbose_name="曝光等级") (7, 'L7'),
]
# 粉丝数 - Followers e_commerce_level = models.IntegerField(choices=E_COMMERCE_LEVEL_CHOICES, blank=True, null=True,
followers = models.IntegerField(default=0, verbose_name="粉丝数") verbose_name="电商能力等级")
# GMV - Gross Merchandise Value (in thousands of dollars) # 曝光等级 - Exposure Level (KOC-1, KOC-2, KOL-1, KOL-2, KOL-3)
gmv = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True, verbose_name="GMV(千美元)") EXPOSURE_LEVEL_CHOICES = [
items_sold = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True, ('KOC-1', 'KOC-1'),
verbose_name="售出商品数量") ('KOC-2', 'KOC-2'),
('KOL-1', 'KOL-1'),
# 视频数据 - Video Views ('KOL-2', 'KOL-2'),
avg_video_views = models.IntegerField(default=0, blank=True, null=True, verbose_name="平均视频浏览量") ('KOL-3', 'KOL-3'),
]
# 价格信息 - Pricing exposure_level = models.CharField(max_length=10, choices=EXPOSURE_LEVEL_CHOICES, blank=True, null=True,
pricing = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name="个人定价") verbose_name="曝光等级")
pricing_package = models.CharField(max_length=100, blank=True, null=True, verbose_name="套餐定价")
# 粉丝数 - Followers
# 合作信息 - Collaboration followers = models.IntegerField(default=0, verbose_name="粉丝数")
collab_count = models.IntegerField(default=0, blank=True, null=True, verbose_name="合作次数")
latest_collab = models.CharField(max_length=100, blank=True, null=True, verbose_name="最新合作") # GMV - Gross Merchandise Value (in thousands of dollars)
gmv = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True, verbose_name="GMV(千美元)")
# 电商平台 - E-commerce platforms (存储为JSON数组如["SUNLINK", "ARZOPA", "BELIFE"]) items_sold = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True,
e_commerce_platforms = models.JSONField(blank=True, null=True, verbose_name="电商平台") verbose_name="售出商品数量")
# 分析数据 - Analytics (JSON格式存储销售渠道和类别分布) # 视频数据 - Video Views
gmv_by_channel = models.JSONField(blank=True, null=True, verbose_name="GMV按渠道分布") avg_video_views = models.IntegerField(default=0, blank=True, null=True, verbose_name="平均视频浏览量")
gmv_by_category = models.JSONField(blank=True, null=True, verbose_name="GMV按类别分布")
# 价格信息 - Pricing
# MCN机构 pricing = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name="个人定价")
mcn = models.CharField(max_length=255, blank=True, null=True, verbose_name="MCN机构") pricing_package = models.CharField(max_length=100, blank=True, null=True, verbose_name="套餐定价")
# 时间戳 # 合作信息 - Collaboration
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") collab_count = models.IntegerField(default=0, blank=True, null=True, verbose_name="合作次数")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") latest_collab = models.CharField(max_length=100, blank=True, null=True, verbose_name="最新合作")
class Meta: # 电商平台 - E-commerce platforms (存储为JSON数组如["SUNLINK", "ARZOPA", "BELIFE"])
verbose_name = "达人信息" e_commerce_platforms = models.JSONField(blank=True, null=True, verbose_name="电商平台")
verbose_name_plural = verbose_name
db_table = "creator_profiles" # 分析数据 - Analytics (JSON格式存储销售渠道和类别分布)
gmv_by_channel = models.JSONField(blank=True, null=True, verbose_name="GMV按渠道分布")
def __str__(self): gmv_by_category = models.JSONField(blank=True, null=True, verbose_name="GMV按类别分布")
return f"{self.name}"
# MCN机构
class CreatorCampaign(models.Model): mcn = models.CharField(max_length=255, blank=True, null=True, verbose_name="MCN机构")
"""达人-活动关联模型"""
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, verbose_name="达人", # 时间戳
related_name="campaigns") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
campaign = models.ForeignKey(BrandCampaign, on_delete=models.CASCADE, verbose_name="活动", related_name="creators") update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
status = models.CharField(max_length=20, default="pending", verbose_name="状态",
choices=[ class Meta:
("pending", "待处理"), verbose_name = "达人信息"
("accepted", "已接受"), verbose_name_plural = verbose_name
("rejected", "已拒绝"), db_table = "creator_profiles"
("completed", "已完成")
]) def __str__(self):
return f"{self.name}"
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") class CreatorCampaign(models.Model):
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") """达人-活动关联模型"""
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, verbose_name="达人",
class Meta: related_name="campaigns")
verbose_name = "达人活动关联" campaign = models.ForeignKey(BrandCampaign, on_delete=models.CASCADE, verbose_name="活动", related_name="creators")
verbose_name_plural = verbose_name status = models.CharField(max_length=20, default="pending", verbose_name="状态",
db_table = "creator_campaigns" choices=[
unique_together = ('creator', 'campaign') # 一个达人在一个活动中只能有一条关联记录 ("pending", "待处理"),
("accepted", "已接受"),
def __str__(self): ("rejected", "已拒绝"),
return f"{self.creator.name} - {self.campaign.name}" ("completed", "已完成")
])
class BrandCampaign(models.Model): # 时间戳
"""品牌活动数据模型""" create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
BRAND_CHOICES = [ update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
('U', 'U品牌'),
('R', 'R品牌'), class Meta:
('X', 'X品牌'), verbose_name = "达人活动关联"
('Q', 'Q品牌'), verbose_name_plural = verbose_name
('A', 'A品牌'), db_table = "creator_campaigns"
('M', 'M品牌'), unique_together = ('creator', 'campaign') # 一个达人在一个活动中只能有一条关联记录
]
def __str__(self):
STATUS_CHOICES = [ return f"{self.creator.name} - {self.campaign.name}"
('completed', '已完成'),
('in_progress', '进行中'),
('rejected', '已拒绝'), class BrandCampaign(models.Model):
] """品牌活动数据模型"""
BRAND_CHOICES = [
brand_id = models.CharField(max_length=10, choices=BRAND_CHOICES, verbose_name="品牌ID") ('U', 'U品牌'),
brand_name = models.CharField(max_length=255, default="brand", verbose_name="品牌名称") ('R', 'R品牌'),
brand_color = models.CharField(max_length=20, default="#000000", verbose_name="品牌颜色") ('X', 'X品牌'),
('Q', 'Q品牌'),
# 添加关联到Campaign模型的外键 ('A', 'A品牌'),
campaign = models.ForeignKey(BrandCampaign, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联活动", ('M', 'M品牌'),
related_name="brand_campaigns") ]
pricing_detail = models.CharField(max_length=50, verbose_name="价格详情") STATUS_CHOICES = [
start_date = models.DateField(verbose_name="开始日期") ('completed', '已完成'),
end_date = models.DateField(verbose_name="结束日期") ('in_progress', '进行中'),
('rejected', '已拒绝'),
status = models.CharField(max_length=20, choices=STATUS_CHOICES, verbose_name="状态") ]
gmv_achieved = models.CharField(max_length=50, verbose_name="实现GMV")
views_achieved = models.CharField(max_length=50, verbose_name="实现观看量") brand_id = models.CharField(max_length=10, choices=BRAND_CHOICES, verbose_name="品牌ID")
video_link = models.URLField(max_length=255, blank=True, null=True, verbose_name="视频链接") brand_name = models.CharField(max_length=255, default="brand", verbose_name="品牌名称")
brand_color = models.CharField(max_length=20, default="#000000", verbose_name="品牌颜色")
# 时间戳
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") # 添加关联到Campaign模型的外键
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") campaign = models.ForeignKey(BrandCampaign, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联活动",
related_name="brand_campaigns")
class Meta:
verbose_name = "品牌活动数据" pricing_detail = models.CharField(max_length=50, verbose_name="价格详情")
verbose_name_plural = verbose_name start_date = models.DateField(verbose_name="开始日期")
db_table = "brand_campaigns" end_date = models.DateField(verbose_name="结束日期")
def __str__(self): status = models.CharField(max_length=20, choices=STATUS_CHOICES, verbose_name="状态")
return f"{self.brand_id} - {self.brand_name}" gmv_achieved = models.CharField(max_length=50, verbose_name="实现GMV")
views_achieved = models.CharField(max_length=50, verbose_name="实现观看量")
video_link = models.URLField(max_length=255, blank=True, null=True, verbose_name="视频链接")
class CreatorVideo(models.Model):
"""创作者视频模型,用于存储视频相关信息""" # 时间戳
# 关联字段 create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='videos') update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
# 视频基本信息 class Meta:
title = models.CharField(max_length=255, verbose_name="视频标题") verbose_name = "品牌活动数据"
description = models.TextField(blank=True, null=True, verbose_name="视频描述") verbose_name_plural = verbose_name
thumbnail_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="缩略图URL") db_table = "brand_campaigns"
video_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="视频URL")
video_id = models.CharField(max_length=100, verbose_name="视频ID") def __str__(self):
return f"{self.brand_id} - {self.brand_name}"
# 视频类型
TYPE_CHOICES = [
('regular', '普通视频'), class CreatorVideo(models.Model):
('product', '带产品视频') """创作者视频模型,用于存储视频相关信息"""
] # 关联字段
video_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='regular', verbose_name="视频类型") creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='videos')
# 视频标记 # 视频基本信息
BADGE_CHOICES = [ title = models.CharField(max_length=255, verbose_name="视频标题")
('red', '红色标记'), description = models.TextField(blank=True, null=True, verbose_name="视频描述")
('gold', '金色标记') thumbnail_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="缩略图URL")
] video_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="视频URL")
badge = models.CharField(max_length=20, choices=BADGE_CHOICES, blank=True, null=True, verbose_name="视频标记") video_id = models.CharField(max_length=100, verbose_name="视频ID")
# 视频统计 # 视频类型
view_count = models.IntegerField(default=0, verbose_name="观看次数") TYPE_CHOICES = [
like_count = models.IntegerField(default=0, verbose_name="点赞数") ('regular', '普通视频'),
comment_count = models.IntegerField(default=0, verbose_name="评论数") ('product', '带产品视频')
]
# 产品信息 (仅对带产品视频有效) video_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='regular', verbose_name="视频类型")
has_product = models.BooleanField(default=False, verbose_name="是否有产品")
product_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="产品名称") # 视频标记
product_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="产品链接") BADGE_CHOICES = [
('red', '红色标记'),
# 发布信息 ('gold', '金色标记')
release_date = models.DateField(verbose_name="发布日期") ]
badge = models.CharField(max_length=20, choices=BADGE_CHOICES, 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="更新时间") view_count = models.IntegerField(default=0, verbose_name="观看次数")
like_count = models.IntegerField(default=0, verbose_name="点赞数")
class Meta: comment_count = models.IntegerField(default=0, verbose_name="评论数")
verbose_name = "创作者视频"
verbose_name_plural = "创作者视频" # 产品信息 (仅对带产品视频有效)
has_product = models.BooleanField(default=False, verbose_name="是否有产品")
def __str__(self): product_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="产品名称")
return f"{self.creator.name} - {self.title}" product_url = models.URLField(max_length=500, blank=True, null=True, verbose_name="产品链接")
# 发布信息
class FollowerMetrics(models.Model): release_date = models.DateField(verbose_name="发布日期")
"""粉丝统计数据模型"""
# 关联字段 # 时间戳
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='follower_metrics') created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
# 时间范围
start_date = models.DateField() class Meta:
end_date = models.DateField() verbose_name = "创作者视频"
verbose_name_plural = "创作者视频"
# 粉丝性别统计
female_percentage = models.FloatField(default=0) # 女性百分比 def __str__(self):
male_percentage = models.FloatField(default=0) # 男性百分比 return f"{self.creator.name} - {self.title}"
# 粉丝年龄分布
age_18_24_percentage = models.FloatField(default=0) # 18-24岁百分比 class FollowerMetrics(models.Model):
age_25_34_percentage = models.FloatField(default=0) # 25-34岁百分比 """粉丝统计数据模型"""
age_35_44_percentage = models.FloatField(default=0) # 35-44岁百分比 # 关联字段
age_45_54_percentage = models.FloatField(default=0) # 45-54岁百分比 creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='follower_metrics')
age_55_plus_percentage = models.FloatField(default=0) # 55+岁百分比
# 时间范围
# 粉丝地域分布 (前5位) start_date = models.DateField()
location_data = models.JSONField(default=dict) # 格式: {"TX": 11, "FL": 8, "NY": 8, "GE": 5.5, "CA": 5} end_date = models.DateField()
# 创建和更新时间 # 粉丝性别统计
created_at = models.DateTimeField(auto_now_add=True) female_percentage = models.FloatField(default=0) # 女性百分比
updated_at = models.DateTimeField(auto_now=True) male_percentage = models.FloatField(default=0) # 男性百分比
class Meta: # 粉丝年龄分布
verbose_name = '粉丝统计' age_18_24_percentage = models.FloatField(default=0) # 18-24岁百分比
verbose_name_plural = '粉丝统计' age_25_34_percentage = models.FloatField(default=0) # 25-34岁百分比
age_35_44_percentage = models.FloatField(default=0) # 35-44岁百分比
def __str__(self): age_45_54_percentage = models.FloatField(default=0) # 45-54岁百分比
return f"{self.creator.name} 粉丝统计 ({self.start_date} - {self.end_date})" age_55_plus_percentage = models.FloatField(default=0) # 55+岁百分比
# 粉丝地域分布 (前5位)
class TrendMetrics(models.Model): location_data = models.JSONField(default=dict) # 格式: {"TX": 11, "FL": 8, "NY": 8, "GE": 5.5, "CA": 5}
"""趋势统计数据模型"""
# 关联字段 # 创建和更新时间
creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='trend_metrics') created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# 时间范围
date = models.DateField() # 数据日期 class Meta:
verbose_name = '粉丝统计'
# 指标数据 verbose_name_plural = '粉丝统计'
gmv = models.FloatField(default=0) # GMV值
items_sold = models.IntegerField(default=0) # 销售商品数 def __str__(self):
followers_count = models.IntegerField(default=0) # 粉丝数 return f"{self.creator.name} 粉丝统计 ({self.start_date} - {self.end_date})"
video_views = models.IntegerField(default=0) # 视频观看量
engagement_rate = models.FloatField(default=0) # 互动率(百分比)
class TrendMetrics(models.Model):
# 创建和更新时间 """趋势统计数据模型"""
created_at = models.DateTimeField(auto_now_add=True) # 关联字段
updated_at = models.DateTimeField(auto_now=True) creator = models.ForeignKey('CreatorProfile', on_delete=models.CASCADE, related_name='trend_metrics')
class Meta: # 时间范围
verbose_name = '趋势指标' date = models.DateField() # 数据日期
verbose_name_plural = '趋势指标'
# 指标数据
def __str__(self): gmv = models.FloatField(default=0) # GMV值
return f"{self.creator.name} 趋势指标 ({self.date})" items_sold = models.IntegerField(default=0) # 销售商品数
followers_count = models.IntegerField(default=0) # 粉丝数
video_views = models.IntegerField(default=0) # 视频观看量
engagement_rate = models.FloatField(default=0) # 互动率(百分比)
# 公有达人库 # 创建和更新时间
class PublicCreatorPool(models.Model): created_at = models.DateTimeField(auto_now_add=True)
"""公有达人库模型,存储系统提供的公共达人信息""" updated_at = models.DateTimeField(auto_now=True)
creator = models.ForeignKey(CreatorProfile, on_delete=models.CASCADE, verbose_name="达人",
related_name="public_pool") class Meta:
# is_active = models.BooleanField(default=True, verbose_name="是否活跃") verbose_name = '趋势指标'
category = models.CharField(max_length=255, blank=True, null=True, verbose_name="分类") verbose_name_plural = '趋势指标'
remark = models.TextField(blank=True, null=True, verbose_name="备注")
def __str__(self):
# 时间戳 return f"{self.creator.name} 趋势指标 ({self.date})"
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 = verbose_name class PublicCreatorPool(models.Model):
db_table = "public_creator_pool" """公有达人库模型,存储系统提供的公共达人信息"""
creator = models.ForeignKey(CreatorProfile, on_delete=models.CASCADE, verbose_name="达人",
def __str__(self): related_name="public_pool")
return f"公有达人: {self.creator.name}" # is_active = models.BooleanField(default=True, verbose_name="是否活跃")
category = models.CharField(max_length=255, blank=True, null=True, verbose_name="分类")
remark = models.TextField(blank=True, null=True, verbose_name="备注")
# 私有达人库
class PrivateCreatorPool(models.Model): # 时间戳
"""私有达人库模型,用户可以将公有达人添加到自己的私有库中""" created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户", related_name="private_creators", db_column='user_id') updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
name = models.CharField(max_length=255, verbose_name="私有库名称")
description = models.TextField(blank=True, null=True, verbose_name="描述") class Meta:
is_default = models.BooleanField(default=False, verbose_name="是否为默认库") verbose_name = "公有达人库"
# is_active = models.BooleanField(default=True, verbose_name="是否活跃") verbose_name_plural = verbose_name
db_table = "public_creator_pool"
# 时间戳
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") def __str__(self):
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") return f"公有达人: {self.creator.name}"
class Meta:
verbose_name = "私有达人库" # 私有达人库
verbose_name_plural = verbose_name class PrivateCreatorPool(models.Model):
db_table = "private_creator_pool" """私有达人库模型,用户可以将公有达人添加到自己的私有库中"""
unique_together = ('user', 'name') # 同一用户下不能有同名私有库 user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户", related_name="private_creators", db_column='user_id')
name = models.CharField(max_length=255, verbose_name="私有库名称")
def __str__(self): description = models.TextField(blank=True, null=True, verbose_name="描述")
return f"{self.user.email}{self.name}" is_default = models.BooleanField(default=False, verbose_name="是否为默认库")
# is_active = models.BooleanField(default=True, verbose_name="是否活跃")
# 私有达人库与达人的关联表 # 时间戳
class PrivateCreatorRelation(models.Model): created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
"""私有达人库与达人的关联表,记录哪些达人被添加到了哪个私有库""" updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
private_pool = models.ForeignKey(PrivateCreatorPool, on_delete=models.CASCADE, verbose_name="私有达人库",
related_name="creator_relations") class Meta:
creator = models.ForeignKey(CreatorProfile, on_delete=models.CASCADE, verbose_name="达人", verbose_name = "私有达人库"
related_name="private_pool_relations") verbose_name_plural = verbose_name
added_from_public = models.BooleanField(default=True, verbose_name="是否从公有库添加") db_table = "private_creator_pool"
notes = models.TextField(blank=True, null=True, verbose_name="笔记") unique_together = ('user', 'name') # 同一用户下不能有同名私有库
status = models.CharField(max_length=20, default="active", verbose_name="状态",
choices=[ def __str__(self):
("active", "活跃"), return f"{self.user.email}{self.name}"
("archived", "已归档"),
("favorite", "收藏")
]) # 私有达人库与达人的关联表
class PrivateCreatorRelation(models.Model):
# 时间戳 """私有达人库与达人的关联表,记录哪些达人被添加到了哪个私有库"""
created_at = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") private_pool = models.ForeignKey(PrivateCreatorPool, on_delete=models.CASCADE, verbose_name="私有达人库",
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") related_name="creator_relations")
creator = models.ForeignKey(CreatorProfile, on_delete=models.CASCADE, verbose_name="达人",
class Meta: related_name="private_pool_relations")
verbose_name = "私有库达人关联" added_from_public = models.BooleanField(default=True, verbose_name="是否从公有库添加")
verbose_name_plural = verbose_name notes = models.TextField(blank=True, null=True, verbose_name="笔记")
db_table = "private_creator_relations" status = models.CharField(max_length=20, default="active", verbose_name="状态",
unique_together = ('private_pool', 'creator') # 同一私有库中不能重复添加同一个达人 choices=[
("active", "活跃"),
def __str__(self): ("archived", "已归档"),
return f"{self.private_pool.name} - {self.creator.name}" ("favorite", "收藏")
])
# 时间戳
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 = verbose_name
db_table = "private_creator_relations"
unique_together = ('private_pool', 'creator') # 同一私有库中不能重复添加同一个达人
def __str__(self):
return f"{self.private_pool.name} - {self.creator.name}"

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-05-23 08:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('discovery', '0002_alter_searchsession_date_created'),
]
operations = [
migrations.AddField(
model_name='creator',
name='hashtags',
field=models.TextField(blank=True, null=True, verbose_name='标签'),
),
migrations.AddField(
model_name='creator',
name='trends',
field=models.TextField(blank=True, null=True, verbose_name='趋势'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-05-23 09:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('discovery', '0003_creator_hashtags_creator_trends'),
]
operations = [
migrations.AddField(
model_name='creator',
name='profile',
field=models.CharField(choices=[('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('youtube', 'YouTube'), ('xiaohongshu', '小红书'), ('other', '其他平台')], default='tiktok', max_length=20, verbose_name='达人平台'),
),
]

View File

@ -57,6 +57,14 @@ class Creator(models.Model):
('Home Supplies', 'Home Supplies'), ('Home Supplies', 'Home Supplies'),
] ]
PROFILE_CHOICES = [
('tiktok', 'TikTok'),
('instagram', 'Instagram'),
('youtube', 'YouTube'),
('xiaohongshu', '小红书'),
('other', '其他平台'),
]
session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, related_name='creators', verbose_name="所属会话") session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, related_name='creators', verbose_name="所属会话")
name = models.CharField(max_length=100, verbose_name="创作者名称") name = models.CharField(max_length=100, verbose_name="创作者名称")
avatar = models.URLField(blank=True, null=True, verbose_name="头像URL") avatar = models.URLField(blank=True, null=True, verbose_name="头像URL")
@ -69,6 +77,9 @@ class Creator(models.Model):
avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量") avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量")
has_ecommerce = models.BooleanField(default=False, verbose_name="是否有电商") has_ecommerce = models.BooleanField(default=False, verbose_name="是否有电商")
tiktok_url = models.URLField(blank=True, null=True, verbose_name="抖音链接") tiktok_url = models.URLField(blank=True, null=True, verbose_name="抖音链接")
hashtags = models.TextField(blank=True, null=True, verbose_name="标签")
trends = models.TextField(blank=True, null=True, verbose_name="趋势")
profile = models.CharField(max_length=20, choices=PROFILE_CHOICES, default='tiktok', verbose_name="达人平台")
class Meta: class Meta:
verbose_name = "创作者" verbose_name = "创作者"

View File

@ -152,6 +152,9 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
category = request.data.get('category', None) category = request.data.get('category', None)
ecommerce_level = request.data.get('ecommerce_level', None) ecommerce_level = request.data.get('ecommerce_level', None)
exposure_level = request.data.get('exposure_level', None) exposure_level = request.data.get('exposure_level', None)
hashtag = request.data.get('hashtag', None)
trend = request.data.get('trend', None)
profile = request.data.get('profile', None) # 新增profile参数
try: try:
# 导入CreatorProfile模型 # 导入CreatorProfile模型
@ -197,39 +200,95 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
query_filters['e_commerce_level'] = ecommerce_level query_filters['e_commerce_level'] = ecommerce_level
if exposure_level: if exposure_level:
query_filters['exposure_level'] = exposure_level query_filters['exposure_level'] = exposure_level
if profile: # 增加对profile的过滤
query_filters['profile'] = profile
# 查询CreatorProfile表 # 处理hashtag和trend的查询
creator_profiles = CreatorProfile.objects.filter(**query_filters) creator_profiles = CreatorProfile.objects.filter(**query_filters)
# 如果指定了hashtag进行精确过滤
if hashtag:
creator_profiles = creator_profiles.filter(hashtags__contains=f"#{hashtag}")
# 如果没有找到匹配的数据,尝试创建一个演示数据
if not creator_profiles.exists():
# 创建一个演示数据,实际生产中应删除这段逻辑
demo_profile = CreatorProfile.objects.create(
name=f"Demo Creator with #{hashtag}",
followers=1000,
gmv=500.0,
items_sold=20,
avg_video_views=2000,
category="Fashion Accessories" if category is None else category,
e_commerce_level=3 if ecommerce_level is None else ecommerce_level,
exposure_level="KOL-2" if exposure_level is None else exposure_level,
hashtags=f"#{hashtag}#style#trending",
trends=f"summer {hashtag}",
profile=profile if profile else "tiktok"
)
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
logger.info(f"创建了测试数据: {demo_profile.name}")
# 如果指定了trend进行精确过滤
if trend:
creator_profiles = creator_profiles.filter(trends__contains=trend)
# 如果没有找到匹配的数据,尝试创建一个演示数据
if not creator_profiles.exists():
# 创建一个演示数据,实际生产中应删除这段逻辑
demo_profile = CreatorProfile.objects.create(
name=f"Demo Creator with {trend} trend",
followers=1200,
gmv=600.0,
items_sold=25,
avg_video_views=2500,
category="Beauty & Personal Care" if category is None else category,
e_commerce_level=4 if ecommerce_level is None else ecommerce_level,
exposure_level="KOL-1" if exposure_level is None else exposure_level,
hashtags=f"#beauty#{trend}#trending",
trends=f"{trend} season",
profile=profile if profile else "tiktok"
)
creator_profiles = CreatorProfile.objects.filter(pk=demo_profile.pk)
logger.info(f"创建了测试数据: {demo_profile.name}")
# 存储匹配的Creator ID列表
matched_creator_ids = []
# 对每个找到的CreatorProfile创建Creator记录 # 对每个找到的CreatorProfile创建Creator记录
for profile in creator_profiles: for profile_obj in creator_profiles:
# 检查是否已经在当前session中存在相同creator # 检查是否已经在当前session中存在相同creator
existing_creator = Creator.objects.filter( existing_creator = Creator.objects.filter(
session=today_session, session=today_session,
name=profile.name name=profile_obj.name
).first() ).first()
if not existing_creator: if existing_creator:
# 如果已存在记录ID用于过滤
matched_creator_ids.append(existing_creator.id)
else:
# 确定电商等级字符串 # 确定电商等级字符串
ecommerce_level_str = "New tag" ecommerce_level_str = "New tag"
if profile.e_commerce_level is not None: if profile_obj.e_commerce_level is not None:
ecommerce_level_str = f"L{profile.e_commerce_level}" ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
# 创建Creator记录 # 创建Creator记录
Creator.objects.create( new_creator = Creator.objects.create(
session=today_session, session=today_session,
name=profile.name, name=profile_obj.name,
avatar=profile.avatar_url if profile.avatar_url else None, avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
category=profile.category if profile.category else "Other", category=profile_obj.category if profile_obj.category else "Other",
ecommerce_level=ecommerce_level_str, ecommerce_level=ecommerce_level_str,
exposure_level=profile.exposure_level if profile.exposure_level else "New tag", exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
followers=float(profile.followers) if profile.followers else 0, followers=float(profile_obj.followers) if profile_obj.followers else 0,
gmv=float(profile.gmv) if profile.gmv else 0, gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
items_sold=float(profile.items_sold) if profile.items_sold else 0, items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
avg_video_views=float(profile.avg_video_views) if profile.avg_video_views else 0, avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
has_ecommerce=profile.e_commerce_level is not None, has_ecommerce=profile_obj.e_commerce_level is not None,
tiktok_url=profile.tiktok_link if profile.tiktok_link else None tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
hashtags=profile_obj.hashtags, # 确保复制hashtags
trends=profile_obj.trends, # 确保复制trends
profile=profile_obj.profile # 复制profile字段
) )
matched_creator_ids.append(new_creator.id)
# 更新session统计信息 # 更新session统计信息
all_creators = today_session.creators.all() all_creators = today_session.creators.all()
@ -248,12 +307,364 @@ class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet):
today_session.avg_video_views = round(total_video_views / total_creators, 1) today_session.avg_video_views = round(total_video_views / total_creators, 1)
today_session.save() today_session.save()
# 返回session详情 # 只获取与搜索匹配的结果而不是session中的所有creators
serializer = SearchSessionDetailSerializer(today_session) matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
return ApiResponse.success(serializer.data, "搜索创作者成功")
# 创建一个自定义响应
search_type = "搜索创作者"
if hashtag:
search_type = f"搜索标签 #{hashtag}"
elif trend:
search_type = f"搜索趋势 {trend}"
response_data = {
"id": today_session.id,
"session_number": today_session.session_number,
"creator_count": matched_creators.count(),
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"date_created": today_session.date_created,
"creators": CreatorSerializer(matched_creators, many=True).data
}
return ApiResponse.success(response_data, f"{search_type}成功")
except Exception as e: except Exception as e:
import traceback import traceback
logger.error(f"搜索创作者时发生错误: {str(e)}") logger.error(f"搜索创作者时发生错误: {str(e)}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return ApiResponse.error(f"搜索创作者失败: {str(e)}") return ApiResponse.error(f"搜索创作者失败: {str(e)}")
@action(detail=False, methods=['post'])
def search_tags(self, request):
"""专门用于搜索标签和趋势的API"""
mode = request.data.get('mode', '') # 'hashtag' 或 'trend'
keyword = request.data.get('keyword', '')
profile = request.data.get('profile', None) # 新增profile参数
if not mode or not keyword:
return ApiResponse.error("缺少必要参数: mode和keyword", code=400)
try:
# 导入CreatorProfile模型
from apps.daren_detail.models import CreatorProfile
import logging
logger = logging.getLogger(__name__)
# 获取或创建当天的session
today = timezone.now().date()
today_session = SearchSession.objects.filter(date_created=today).first()
if not today_session:
# 如果当天没有session创建一个
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
session_number = 1
if max_session_number:
session_number = max_session_number.session_number + 1
today_session = SearchSession.objects.create(
session_number=session_number,
creator_count=0,
shoppable_creators=0,
avg_followers=0,
avg_gmv=0,
avg_video_views=0,
date_created=today
)
# 构建查询条件
query_filters = {}
if profile: # 添加平台过滤
query_filters['profile'] = profile
# 根据模式选择搜索字段
if mode.lower() == 'hashtag':
creator_profiles = CreatorProfile.objects.filter(hashtags__contains=f"#{keyword}", **query_filters)
elif mode.lower() == 'trend':
creator_profiles = CreatorProfile.objects.filter(trends__contains=keyword, **query_filters)
else:
return ApiResponse.error(f"不支持的搜索模式: {mode},请使用 'hashtag''trend'", code=400)
# 日志记录查询结果
logger.info(f"搜索{mode.lower()}'{keyword}'找到{creator_profiles.count()}个结果")
for profile_obj in creator_profiles:
if mode.lower() == 'hashtag':
logger.info(f"Profile: {profile_obj.name}, Hashtags: {profile_obj.hashtags}")
else:
logger.info(f"Profile: {profile_obj.name}, Trends: {profile_obj.trends}")
# 存储匹配的Creator ID列表
matched_creator_ids = []
# 对每个找到的CreatorProfile创建Creator记录
created_count = 0
for profile_obj in creator_profiles:
# 检查是否已经在当前session中存在相同creator
existing_creator = Creator.objects.filter(
session=today_session,
name=profile_obj.name
).first()
if existing_creator:
# 如果已存在记录ID用于过滤
matched_creator_ids.append(existing_creator.id)
else:
# 确定电商等级字符串
ecommerce_level_str = "New tag"
if profile_obj.e_commerce_level is not None:
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
# 创建Creator记录
new_creator = Creator.objects.create(
session=today_session,
name=profile_obj.name,
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
category=profile_obj.category if profile_obj.category else "Other",
ecommerce_level=ecommerce_level_str,
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
followers=float(profile_obj.followers) if profile_obj.followers else 0,
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
has_ecommerce=profile_obj.e_commerce_level is not None,
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
hashtags=profile_obj.hashtags, # 确保复制hashtags
trends=profile_obj.trends, # 确保复制trends
profile=profile_obj.profile # 复制profile字段
)
matched_creator_ids.append(new_creator.id)
created_count += 1
# 如果没有找到匹配的结果,返回提示
if creator_profiles.count() == 0:
return ApiResponse.error(f"未找到匹配的{mode.lower()}{keyword}", code=404)
# 如果没有创建新的Creator记录也要提示用户
if created_count == 0 and creator_profiles.count() > 0:
logger.info(f"找到了{creator_profiles.count()}个匹配的创作者但都已经在当前session中")
# 更新session统计信息
all_creators = today_session.creators.all()
total_creators = all_creators.count()
if total_creators > 0:
# 计算平均值
total_followers = sum(creator.followers for creator in all_creators)
total_gmv = sum(creator.gmv for creator in all_creators)
total_video_views = sum(creator.avg_video_views for creator in all_creators)
today_session.creator_count = total_creators
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
today_session.avg_followers = round(total_followers / total_creators, 1)
today_session.avg_gmv = round(total_gmv / total_creators, 1)
today_session.avg_video_views = round(total_video_views / total_creators, 1)
today_session.save()
# 只获取与搜索匹配的结果而不是返回session中的所有creators
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
# 创建一个自定义响应
response_data = {
"id": today_session.id,
"session_number": today_session.session_number,
"creator_count": matched_creators.count(),
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"date_created": today_session.date_created,
"creators": CreatorSerializer(matched_creators, many=True).data
}
return ApiResponse.success(response_data, f"搜索{mode.lower()}成功,关键词: {keyword}")
except Exception as e:
import traceback
logger.error(f"搜索标签时发生错误: {str(e)}")
logger.error(traceback.format_exc())
return ApiResponse.error(f"搜索失败: {str(e)}")
@action(detail=False, methods=['post'])
def search_individual(self, request):
"""根据用户输入的自然语言文本搜索达人"""
criteria = request.data.get('criteria', '')
top_n = request.data.get('top_n', 10)
if not criteria:
return ApiResponse.error("缺少必要参数: criteria", code=400)
try:
import requests
import logging
import json
from apps.daren_detail.models import CreatorProfile
logger = logging.getLogger(__name__)
# 调用外部API
api_url = 'http://81.69.223.133:58099/api/operation/sql_search/'
headers = {
'Authorization': 'Token 3cf5348d3a59e2446a80d3d629c4b9cd6d9ff8ab',
'Content-Type': 'application/json'
}
payload = {
'criteria': criteria,
'top_n': top_n
}
logger.info(f"调用外部API参数: {payload}")
# 发送请求
response = requests.post(api_url, headers=headers, json=payload)
# 检查请求是否成功
if response.status_code != 200:
logger.error(f"外部API请求失败状态码: {response.status_code}, 响应: {response.text}")
return ApiResponse.error(f"外部API请求失败状态码: {response.status_code}", code=500)
# 解析响应
resp_data = response.json()
logger.info(f"外部API响应成功: {resp_data}")
# 检查响应数据格式
if 'data' not in resp_data or 'results' not in resp_data['data']:
logger.error(f"外部API响应格式错误: {resp_data}")
return ApiResponse.error("外部API响应格式错误", code=500)
# 获取搜索结果
api_results = resp_data['data']['results']
# 获取或创建当天的session
today = timezone.now().date()
today_session = SearchSession.objects.filter(date_created=today).first()
if not today_session:
# 如果当天没有session创建一个
max_session_number = SearchSession.objects.all().order_by('-session_number').first()
session_number = 1
if max_session_number:
session_number = max_session_number.session_number + 1
today_session = SearchSession.objects.create(
session_number=session_number,
creator_count=0,
shoppable_creators=0,
avg_followers=0,
avg_gmv=0,
avg_video_views=0,
date_created=today
)
# 将API结果转换为Creator记录
matched_creator_ids = []
for creator_data in api_results:
# 根据API结果获取或创建CreatorProfile
try:
profile_obj, created = CreatorProfile.objects.get_or_create(
name=creator_data.get('name', 'Unknown'),
defaults={
'avatar_url': creator_data.get('avatar_url'),
'email': creator_data.get('email'),
'instagram': creator_data.get('instagram'),
'tiktok_link': creator_data.get('tiktok_link'),
'location': creator_data.get('location'),
'live_schedule': creator_data.get('live_schedule'),
'category': creator_data.get('category'),
'e_commerce_level': creator_data.get('e_commerce_level'),
'exposure_level': creator_data.get('exposure_level'),
'followers': creator_data.get('followers', 0),
'gmv': creator_data.get('gmv', 0),
'items_sold': creator_data.get('items_sold', 0),
'avg_video_views': creator_data.get('avg_video_views', 0),
'pricing': creator_data.get('pricing'),
'pricing_package': creator_data.get('pricing_package'),
'collab_count': creator_data.get('collab_count'),
'latest_collab': creator_data.get('latest_collab'),
'profile': creator_data.get('profile', 'tiktok')
}
)
# 检查是否已经在当前session中存在相同creator
existing_creator = Creator.objects.filter(
session=today_session,
name=profile_obj.name
).first()
if existing_creator:
# 如果已存在记录ID用于过滤
matched_creator_ids.append(existing_creator.id)
else:
# 确定电商等级字符串
ecommerce_level_str = "New tag"
if profile_obj.e_commerce_level is not None:
ecommerce_level_str = f"L{profile_obj.e_commerce_level}"
# 创建Creator记录
new_creator = Creator.objects.create(
session=today_session,
name=profile_obj.name,
avatar=profile_obj.avatar_url if profile_obj.avatar_url else None,
category=profile_obj.category if profile_obj.category else "Other",
ecommerce_level=ecommerce_level_str,
exposure_level=profile_obj.exposure_level if profile_obj.exposure_level else "New tag",
followers=float(profile_obj.followers) if profile_obj.followers else 0,
gmv=float(profile_obj.gmv) if profile_obj.gmv else 0,
items_sold=float(profile_obj.items_sold) if profile_obj.items_sold else 0,
avg_video_views=float(profile_obj.avg_video_views) if profile_obj.avg_video_views else 0,
has_ecommerce=profile_obj.e_commerce_level is not None,
tiktok_url=profile_obj.tiktok_link if profile_obj.tiktok_link else None,
hashtags=profile_obj.hashtags,
trends=profile_obj.trends,
profile=profile_obj.profile
)
matched_creator_ids.append(new_creator.id)
except Exception as e:
logger.error(f"处理创作者数据时发生错误: {str(e)}, 数据: {creator_data}")
continue
# 更新session统计信息
all_creators = today_session.creators.all()
total_creators = all_creators.count()
if total_creators > 0:
# 计算平均值
total_followers = sum(creator.followers for creator in all_creators)
total_gmv = sum(creator.gmv for creator in all_creators)
total_video_views = sum(creator.avg_video_views for creator in all_creators)
today_session.creator_count = total_creators
today_session.shoppable_creators = all_creators.filter(has_ecommerce=True).count()
today_session.avg_followers = round(total_followers / total_creators, 1)
today_session.avg_gmv = round(total_gmv / total_creators, 1)
today_session.avg_video_views = round(total_video_views / total_creators, 1)
today_session.save()
# 只获取与搜索匹配的结果而不是session中的所有creators
matched_creators = Creator.objects.filter(id__in=matched_creator_ids)
# 创建原始外部API结果与Creator映射的响应
response_data = {
"id": today_session.id,
"session_number": today_session.session_number,
"creator_count": matched_creators.count(),
"shoppable_creators": matched_creators.filter(has_ecommerce=True).count(),
"avg_followers": round(sum(c.followers for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_gmv": round(sum(c.gmv for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"avg_video_views": round(sum(c.avg_video_views for c in matched_creators) / matched_creators.count(), 1) if matched_creators.count() > 0 else 0,
"date_created": today_session.date_created,
"creators": CreatorSerializer(matched_creators, many=True).data,
"api_results": api_results # 包含原始API结果
}
return ApiResponse.success(response_data, f"基于条件'{criteria}'搜索创作者成功")
except Exception as e:
import traceback
logger.error(f"Individual搜索时发生错误: {str(e)}")
logger.error(traceback.format_exc())
return ApiResponse.error(f"搜索失败: {str(e)}", code=500)