From 38a1a0a808e9cff2a3a06cc6c7ccbfad5a739df5 Mon Sep 17 00:00:00 2001 From: jlj <3042504846@qq.com> Date: Fri, 23 May 2025 12:11:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BF=87=E6=BB=A4=E5=92=8C?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/brands/consumers.py | 2 +- apps/brands/services/offer_status_service.py | 2 +- apps/brands/views.py | 8 +- .../management/commands/populate_data.py | 365 ------------- ...ove_creatorprofile_pricing_max_and_more.py | 26 + apps/daren_detail/models.py | 3 +- apps/daren_detail/urls.py | 2 + apps/daren_detail/views.py | 498 ++++++++++++++++-- apps/user/authentication.py | 20 +- 9 files changed, 515 insertions(+), 411 deletions(-) delete mode 100644 apps/daren_detail/management/commands/populate_data.py create mode 100644 apps/daren_detail/migrations/0002_remove_creatorprofile_pricing_max_and_more.py diff --git a/apps/brands/consumers.py b/apps/brands/consumers.py index 67479c2..00531e6 100644 --- a/apps/brands/consumers.py +++ b/apps/brands/consumers.py @@ -227,7 +227,7 @@ class CampaignStatusConsumer(WebsocketConsumer): "followers": f"{int(creator.followers / 1000)}k" if creator.followers else "0", "gmv_generated": f"${creator.gmv}k" if creator.gmv else "$0", "views_generated": f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0", - "pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "pricing": f"${creator.pricing}" if creator.pricing else "$0", "status": status } creator_list.append(creator_data) diff --git a/apps/brands/services/offer_status_service.py b/apps/brands/services/offer_status_service.py index 02a28de..e4aa312 100644 --- a/apps/brands/services/offer_status_service.py +++ b/apps/brands/services/offer_status_service.py @@ -151,7 +151,7 @@ class OfferStatusService: "followers": followers_formatted, "gmv_generated": f"${creator.gmv}k" if creator.gmv else "$0", "views_generated": avg_views_formatted, - "pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "pricing": f"${creator.pricing}" if creator.pricing else "$0", "status": status } creator_list.append(creator_data) diff --git a/apps/brands/views.py b/apps/brands/views.py index 5bd4849..2bea480 100644 --- a/apps/brands/views.py +++ b/apps/brands/views.py @@ -365,7 +365,7 @@ class CampaignViewSet(viewsets.ModelViewSet): "followers": f"{int(creator.followers / 1000)}k" if creator.followers else "0", "GMV Generated": f"${creator.gmv}k" if creator.gmv else "$0", "Views Generated": f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0", - "Pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "Pricing": f"${creator.pricing}" if creator.pricing else "$0", "Status": cc.status } all_creator_list.append(creator_data) @@ -521,7 +521,7 @@ class CampaignViewSet(viewsets.ModelViewSet): "followers": f"{int(creator.followers / 1000)}k" if creator.followers else "0", "gmv_generated": f"${creator.gmv}k" if creator.gmv else "$0", "views_generated": f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0", - "pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "pricing": f"${creator.pricing}" if creator.pricing else "$0", "status": status } creator_list.append(creator_data) @@ -589,7 +589,7 @@ class CampaignViewSet(viewsets.ModelViewSet): "followers": f"{int(creator.followers / 1000)}k" if creator.followers else "0", "gmv_generated": f"${creator.gmv}k" if creator.gmv else "$0", "views_generated": f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0", - "pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "pricing": f"${creator.pricing}" if creator.pricing else "$0", "status": status } creator_list.append(creator_data) @@ -648,7 +648,7 @@ class CampaignViewSet(viewsets.ModelViewSet): "followers": f"{int(creator.followers / 1000)}k" if creator.followers else "0", "gmv_generated": f"${creator.gmv}k" if creator.gmv else "$0", "views_generated": f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0", - "pricing": f"${creator.pricing_min}" if creator.pricing_min else "$0", + "pricing": f"${creator.pricing}" if creator.pricing else "$0", "status": status } creator_list.append(creator_data) diff --git a/apps/daren_detail/management/commands/populate_data.py b/apps/daren_detail/management/commands/populate_data.py deleted file mode 100644 index 93c2de5..0000000 --- a/apps/daren_detail/management/commands/populate_data.py +++ /dev/null @@ -1,365 +0,0 @@ -from django.core.management.base import BaseCommand -from django.utils import timezone -from apps.daren_detail.models import ( - CollaborationMetrics, VideoMetrics, LiveMetrics, CreatorProfile, - CreatorCampaign, BrandCampaign, CreatorVideo, FollowerMetrics, - TrendMetrics, PublicCreatorPool, PrivateCreatorPool, PrivateCreatorRelation -) -from apps.expertproducts.models import Product as ExpertProduct, Creator as ExpertCreator, Negotiation, Message -from apps.discovery.models import SearchSession, Creator as DiscoveryCreator -from apps.brands.models import Brand, Product as BrandProduct, Campaign, BrandChatSession -from apps.template.models import TemplateCategory, Template -from apps.user.models import User -import random -from datetime import datetime, timedelta -import uuid - -class Command(BaseCommand): - help = '填充测试数据到所有相关模型' - - def handle(self, *args, **kwargs): - self.stdout.write('开始填充数据...') - - # 创建用户(避免重复) - user, created = User.objects.get_or_create( - email='test@example.com', - defaults={ - 'password': 'testpassword', - 'company': '测试公司', - 'name': '测试用户', - 'is_first_login': False, - 'last_login': timezone.now() - } - ) - - # 创建品牌(避免重复) - brands = [] - brand_names = ['U品牌', 'R品牌', 'X品牌', 'Q品牌', 'A品牌', 'M品牌'] - for name in brand_names: - brand, _ = Brand.objects.get_or_create( - name=name, - defaults={ - 'description': f'{name}的描述信息', - 'logo_url': f'https://example.com/logos/{name}.png', - 'category': '电子产品', - 'source': '内部', - 'collab_count': random.randint(5, 20), - 'creators_count': random.randint(10, 50), - 'campaign_id': str(uuid.uuid4()), - 'total_gmv_achieved': random.randint(10000, 100000), - 'total_views_achieved': random.randint(100000, 1000000), - 'shop_overall_rating': round(random.uniform(3.5, 5.0), 1), - 'dataset_id_list': [str(uuid.uuid4()) for _ in range(3)] - } - ) - brands.append(brand) - - # 创建品牌产品(避免重复) - products = [] - for brand in brands: - for i in range(3): - product, _ = BrandProduct.objects.get_or_create( - brand=brand, - name=f'{brand.name}产品{i+1}', - defaults={ - 'description': f'{brand.name}产品{i+1}的详细描述', - 'image_url': f'https://example.com/products/{brand.name}_{i+1}.jpg', - 'pid': f'PID_{uuid.uuid4().hex[:8]}', - 'commission_rate': random.uniform(5, 20), - 'open_collab': random.uniform(10, 30), - 'available_samples': random.randint(10, 100), - 'sales_price_min': random.uniform(100, 500), - 'sales_price_max': random.uniform(500, 2000), - 'stock': random.randint(100, 1000), - 'items_sold': random.randint(50, 500), - 'product_rating': round(random.uniform(3.5, 5.0), 1), - 'reviews_count': random.randint(10, 100), - 'collab_creators': random.randint(5, 20), - 'tiktok_shop': random.choice([True, False]), - 'dataset_id': str(uuid.uuid4()) - } - ) - products.append(product) - - # 创建活动(避免重复) - campaigns = [] - for brand in brands: - campaign, _ = Campaign.objects.get_or_create( - brand=brand, - name=f'{brand.name}活动', - defaults={ - 'description': f'{brand.name}的活动描述', - 'image_url': f'https://example.com/campaigns/{brand.name}.jpg', - 'service': '直播带货', - 'creator_type': 'KOL', - 'creator_level': 'L3', - 'creator_category': '电子产品', - 'creators_count': random.randint(5, 20), - 'gmv': '10000-50000', - 'followers': '10000-100000', - 'views': '100000-1000000', - 'budget': '5000-20000', - 'start_date': timezone.now(), - 'end_date': timezone.now() + timedelta(days=30), - 'dataset_id': str(uuid.uuid4()), - 'status': 'in_progress', - 'gmv_achieved': '25000', - 'views_achieved': '500000', - 'video_link': 'https://example.com/videos/campaign.mp4' - } - ) - campaign.link_product.set(random.sample(products, 2)) - campaigns.append(campaign) - - # 创建创作者档案 - creators = [] - for i in range(10): - creator = CreatorProfile.objects.create( - name=f'创作者{i+1}', - avatar_url=f'https://example.com/avatars/creator_{i+1}.jpg', - email=f'creator{i+1}@example.com', - instagram=f'creator{i+1}', - tiktok_link=f'https://tiktok.com/@{i+1}', - location='上海', - live_schedule='每周一、三、五 20:00-22:00', - category=random.choice([c[0] for c in CreatorProfile.CATEGORY_CHOICES]), - e_commerce_level=random.randint(1, 7), - exposure_level=random.choice([e[0] for e in CreatorProfile.EXPOSURE_LEVEL_CHOICES]), - followers=random.randint(10000, 1000000), - gmv=random.uniform(10000, 1000000), - items_sold=random.uniform(1000, 10000), - avg_video_views=random.randint(10000, 100000), - pricing_min=random.uniform(1000, 5000), - pricing_max=random.uniform(5000, 20000), - pricing_package='基础套餐', - collab_count=random.randint(5, 50), - latest_collab='最新合作项目', - e_commerce_platforms=['TikTok', 'Instagram'], - gmv_by_channel={'TikTok': 60, 'Instagram': 40}, - gmv_by_category={'电子产品': 70, '服装': 30}, - mcn='知名MCN机构' - ) - creators.append(creator) - - # 创建协作指标 - for creator in creators: - CollaborationMetrics.objects.create( - avg_commission_rate=random.uniform(5, 20), - products_count=random.randint(10, 50), - brand_collaborations=random.randint(5, 20), - min_product_price=random.uniform(100, 500), - max_product_price=random.uniform(500, 2000), - start_date=timezone.now().date(), - end_date=timezone.now().date() + timedelta(days=30), - creator=creator - ) - - # 创建视频指标 - for creator in creators: - for video_type in ['regular', 'shoppable']: - VideoMetrics.objects.create( - video_type=video_type, - gpm=random.uniform(100, 1000), - videos_count=random.randint(10, 50), - avg_views=random.uniform(10000, 100000), - avg_engagement=random.uniform(1, 10), - avg_likes=random.randint(1000, 10000), - start_date=timezone.now().date(), - end_date=timezone.now().date() + timedelta(days=30), - creator=creator - ) - - # 创建直播指标 - for creator in creators: - for live_type in ['regular', 'shoppable']: - LiveMetrics.objects.create( - live_type=live_type, - gpm=random.uniform(100, 1000), - lives_count=random.randint(5, 20), - avg_views=random.uniform(10000, 100000), - avg_engagement=random.uniform(1, 10), - avg_likes=random.randint(1000, 10000), - start_date=timezone.now().date(), - end_date=timezone.now().date() + timedelta(days=30), - creator=creator - ) - - # 创建创作者视频 - for creator in creators: - for i in range(3): - CreatorVideo.objects.create( - creator=creator, - title=f'视频标题{i+1}', - description=f'视频描述{i+1}', - thumbnail_url=f'https://example.com/thumbnails/video_{i+1}.jpg', - video_url=f'https://example.com/videos/video_{i+1}.mp4', - video_id=f'VID_{uuid.uuid4().hex[:8]}', - video_type=random.choice(['regular', 'product']), - badge=random.choice(['red', 'gold']), - view_count=random.randint(10000, 100000), - like_count=random.randint(1000, 10000), - comment_count=random.randint(100, 1000), - has_product=random.choice([True, False]), - product_name=f'产品{i+1}' if random.choice([True, False]) else None, - product_url=f'https://example.com/products/{i+1}' if random.choice([True, False]) else None, - release_date=timezone.now().date() - ) - - # 创建粉丝指标 - for creator in creators: - FollowerMetrics.objects.create( - creator=creator, - start_date=timezone.now().date(), - end_date=timezone.now().date() + timedelta(days=30), - female_percentage=random.uniform(40, 60), - male_percentage=random.uniform(40, 60), - age_18_24_percentage=random.uniform(20, 40), - age_25_34_percentage=random.uniform(30, 50), - age_35_44_percentage=random.uniform(10, 30), - age_45_54_percentage=random.uniform(5, 15), - age_55_plus_percentage=random.uniform(1, 10), - location_data={ - '上海': random.uniform(10, 30), - '北京': random.uniform(10, 30), - '广州': random.uniform(5, 20), - '深圳': random.uniform(5, 20), - '杭州': random.uniform(5, 15) - } - ) - - # 创建趋势指标 - for creator in creators: - for i in range(10): - TrendMetrics.objects.create( - creator=creator, - date=timezone.now().date() - timedelta(days=i), - gmv=random.uniform(1000, 10000), - items_sold=random.randint(100, 1000), - followers_count=random.randint(10000, 100000), - video_views=random.randint(10000, 100000), - engagement_rate=random.uniform(1, 10) - ) - - # 创建公有达人库 - for creator in creators: - PublicCreatorPool.objects.create( - creator=creator, - category=creator.category, - remark=f'备注信息:{creator.name}的详细信息' - ) - - # 创建私有达人库(避免重复) - private_pool, _ = PrivateCreatorPool.objects.get_or_create( - user=user, - name='我的收藏', - defaults={ - 'description': '收藏的达人列表', - 'is_default': True - } - ) - - # 创建私有达人关联 - for creator in random.sample(creators, 5): - PrivateCreatorRelation.objects.create( - private_pool=private_pool, - creator=creator, - added_from_public=True, - notes=f'关于{creator.name}的笔记', - status=random.choice(['active', 'archived', 'favorite']) - ) - - # 创建专家产品 - expert_products = [] - for i in range(10): - p = ExpertProduct.objects.create( - name=f'专家产品{i+1}', - category='电子产品', - max_price=random.uniform(1000, 5000), - min_price=random.uniform(500, 1000), - description=f'专家产品{i+1}的详细描述' - ) - expert_products.append(p) - - # 创建专家创作者 - expert_creators = [] - for i in range(10): - c = ExpertCreator.objects.create( - name=f'专家创作者{i+1}', - sex=random.choice(['男', '女']), - age=random.randint(18, 45), - category='带货类', - followers=random.randint(10000, 1000000) - ) - expert_creators.append(c) - - # 创建谈判记录 - for i in range(10): - negotiation = Negotiation.objects.create( - creator=expert_creators[i], - product=expert_products[i], - status=random.choice(['brand_review', 'price_negotiation', 'contract_review']), - current_round=random.randint(1, 5), - context={'current_price': random.uniform(500, 2000)} - ) - - # 为每个谈判创建消息 - for j in range(3): - Message.objects.create( - negotiation=negotiation, - role=random.choice(['user', 'assistant']), - content=f'谈判消息{j+1}', - stage=negotiation.status - ) - - # 创建搜索会话 - for i in range(10): - session = SearchSession.objects.create( - session_number=i+1, - creator_count=random.randint(5, 20), - shoppable_creators=random.randint(2, 10), - avg_followers=random.uniform(10000, 100000), - avg_gmv=random.uniform(10000, 100000), - avg_video_views=random.uniform(10000, 100000) - ) - - # 为每个会话创建创作者 - for j in range(5): - DiscoveryCreator.objects.create( - session=session, - name=f'发现创作者{j+1}', - avatar=f'https://example.com/avatars/discovery_{j+1}.jpg', - category=random.choice([c[0] for c in DiscoveryCreator.CATEGORIES]), - ecommerce_level=random.choice([l[0] for l in DiscoveryCreator.ECOMMERCE_LEVELS]), - exposure_level=random.choice([l[0] for l in DiscoveryCreator.EXPOSURE_LEVELS]), - followers=random.uniform(10000, 1000000), - gmv=random.uniform(10000, 100000), - items_sold=random.uniform(1000, 10000), - avg_video_views=random.uniform(10000, 100000), - has_ecommerce=random.choice([True, False]), - tiktok_url=f'https://tiktok.com/@{j+1}' - ) - - # 创建模板分类 - categories = [] - for i in range(5): - category = TemplateCategory.objects.create( - name=f'模板分类{i+1}', - description=f'模板分类{i+1}的描述' - ) - categories.append(category) - - # 创建模板 - for category in categories: - for i in range(2): - Template.objects.create( - title=f'模板{i+1}', - content=f'这是模板{i+1}的详细内容,包含了很多有用的信息。', - category=category, - mission=random.choice([m[0] for m in Template.MISSION_CHOICES]), - platform=random.choice([p[0] for p in Template.PLATFORM_CHOICES]), - collaboration_type=random.choice([c[0] for c in Template.COLLABORATION_CHOICES]), - service=random.choice([s[0] for s in Template.SERVICE_CHOICES]), - is_public=random.choice([True, False]) - ) - - self.stdout.write(self.style.SUCCESS('数据填充完成!')) \ No newline at end of file diff --git a/apps/daren_detail/migrations/0002_remove_creatorprofile_pricing_max_and_more.py b/apps/daren_detail/migrations/0002_remove_creatorprofile_pricing_max_and_more.py new file mode 100644 index 0000000..87b0f0c --- /dev/null +++ b/apps/daren_detail/migrations/0002_remove_creatorprofile_pricing_max_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.5 on 2025-05-23 03:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('daren_detail', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='creatorprofile', + name='pricing_max', + ), + migrations.RemoveField( + model_name='creatorprofile', + name='pricing_min', + ), + migrations.AddField( + model_name='creatorprofile', + name='pricing', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='个人定价'), + ), + ] diff --git a/apps/daren_detail/models.py b/apps/daren_detail/models.py index 8a7f847..3bf0856 100644 --- a/apps/daren_detail/models.py +++ b/apps/daren_detail/models.py @@ -190,8 +190,7 @@ class CreatorProfile(models.Model): avg_video_views = models.IntegerField(default=0, blank=True, null=True, verbose_name="平均视频浏览量") # 价格信息 - Pricing - pricing_min = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name="最低个人定价") - pricing_max = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name="最高个人定价") + pricing = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name="个人定价") pricing_package = models.CharField(max_length=100, blank=True, null=True, verbose_name="套餐定价") # 合作信息 - Collaboration diff --git a/apps/daren_detail/urls.py b/apps/daren_detail/urls.py index 8372593..0269826 100644 --- a/apps/daren_detail/urls.py +++ b/apps/daren_detail/urls.py @@ -59,6 +59,7 @@ urlpatterns = [ # 公有达人和私有达人API path('public/creators/', views.get_public_creators, name='get_public_creators'), + path('public/creators/filter/', views.filter_public_creators, name='filter_public_creators'), path('public/creators/add/', views.add_to_public_pool, name='add_to_public_pool'), # 私有达人库 @@ -69,4 +70,5 @@ urlpatterns = [ path('private/pools/creators/add/', views.add_creator_to_private_pool, name='add_creator_to_private_pool'), path('private/pools/creators/update/', views.update_creator_in_private_pool, name='update_creator_in_private_pool'), path('private/pools/creators/remove/', views.remove_creator_from_private_pool, name='remove_creator_from_private_pool'), + path('private/pools/creators/filter/', views.filter_private_pool_creators, name='filter_private_pool_creators'), ] diff --git a/apps/daren_detail/views.py b/apps/daren_detail/views.py index 34d9de9..87ee5b7 100644 --- a/apps/daren_detail/views.py +++ b/apps/daren_detail/views.py @@ -135,7 +135,8 @@ def filter_creators(request): min_price, max_price = pricing_val.split('-') min_price = float(min_price) max_price = float(max_price) - query = query.filter(pricing_min__gte=min_price, pricing_max__lte=max_price) + # 修改:根据单一定价字段判断是否在区间内 + query = query.filter(pricing__gte=min_price, pricing__lte=max_price) # 获取总数据量 total_count = query.count() @@ -159,11 +160,8 @@ def filter_creators(request): followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0" avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0" - # 格式化价格区间 - if creator.pricing_min is not None and creator.pricing_max is not None: - pricing_range = f"${creator.pricing_min}-{creator.pricing_max}" - else: - pricing_range = None + # 格式化价格 + pricing_formatted = f"${creator.pricing}" if creator.pricing else None # 格式化结果 formatted_creator = { @@ -178,10 +176,8 @@ def filter_creators(request): "GMV": gmv_formatted, "Items Sold": f"{creator.items_sold}k" if creator.items_sold else None, "Avg. Video Views": avg_views_formatted, - "Pricing": { - "Range": pricing_range, - "Pack. P": creator.pricing_package - }, + "Pricing": pricing_formatted, + "Pricing Package": creator.pricing_package, "# Collab": creator.collab_count, "Latest Collab.": creator.latest_collab, "E-commerce": creator.e_commerce_platforms, @@ -285,10 +281,17 @@ def add_creator(request): creator.avg_video_views = avg_video_views # 更新价格信息 - pricing = data.get('pricing', {}) - if pricing: - creator.pricing_individual = pricing.get('individual', creator.pricing_individual) - creator.pricing_package = pricing.get('package', creator.pricing_package) + pricing_data = data.get('pricing', {}) + if pricing_data: + # 处理individual价格或直接的pricing值 + if 'individual' in pricing_data: + creator.pricing = pricing_data.get('individual') + elif 'pricing' in pricing_data: + creator.pricing = pricing_data.get('pricing') + elif isinstance(pricing_data, (int, float)): + creator.pricing = pricing_data + + creator.pricing_package = pricing_data.get('package', creator.pricing_package) if isinstance(pricing_data, dict) else creator.pricing_package creator.collab_count = data.get('collab_count', creator.collab_count) creator.latest_collab = data.get('latest_collab', creator.latest_collab) @@ -508,9 +511,9 @@ def get_creator_detail(request, creator_id): gmv_per_customer = 0 if creator.avg_video_views and creator.avg_video_views > 0: - if creator.pricing_min: + if creator.pricing: try: - price = float(creator.pricing_min) + price = float(creator.pricing) gpm = (price * 1000) / creator.avg_video_views gpm = round(gpm, 2) except (ValueError, AttributeError): @@ -554,7 +557,7 @@ def get_creator_detail(request, creator_id): creator.category] if creator.category else [], "mcn": creator.mcn or "", "pricing": { - "range": f"${creator.pricing_min}-{creator.pricing_max}" if creator.pricing_min is not None and creator.pricing_max is not None else None, + "price": f"${creator.pricing}" if creator.pricing else None, "package": creator.pricing_package }, "collab_count": creator.collab_count, @@ -2017,7 +2020,7 @@ def get_public_creators(request): category = request.GET.get('category') keyword = request.GET.get('keyword') - # 基础查询 + # 基础查询 - 从公有达人池开始 public_creators = PublicCreatorPool.objects.all() # 应用过滤条件 @@ -2055,11 +2058,8 @@ def get_public_creators(request): followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0" avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0" - # 格式化价格区间 - if creator.pricing_min is not None and creator.pricing_max is not None: - pricing_range = f"${creator.pricing_min}-{creator.pricing_max}" - else: - pricing_range = None + # 格式化价格 + pricing_formatted = f"${creator.pricing}" if creator.pricing else None # 格式化结果 formatted_creator = { @@ -2073,7 +2073,8 @@ def get_public_creators(request): "followers": followers_formatted, "gmv": gmv_formatted, "avg_video_views": avg_views_formatted, - "pricing": pricing_range, + "pricing": pricing_formatted, + "pricing_package": creator.pricing_package, "collab_count": creator.collab_count, "remark": public_creator.remark, "category_public": public_creator.category @@ -2370,12 +2371,12 @@ def get_private_pool_creators(request, pool_id=None): # 检查pool_id是否提供 if not pool_id: pool_id = request.GET.get('pool_id') - if not pool_id: - return JsonResponse({ - 'code': 400, - 'message': '缺少必要参数: pool_id', - 'data': None - }, json_dumps_params={'ensure_ascii': False}) + if not pool_id: + return JsonResponse({ + 'code': 400, + 'message': '缺少必要参数: pool_id', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) # 获取分页参数 page = int(request.GET.get('page', 1)) @@ -2438,8 +2439,8 @@ def get_private_pool_creators(request, pool_id=None): avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0" # 格式化价格区间 - if creator.pricing_min is not None and creator.pricing_max is not None: - pricing_range = f"${creator.pricing_min}-{creator.pricing_max}" + if creator.pricing is not None: + pricing_range = f"${creator.pricing}" else: pricing_range = None @@ -2766,6 +2767,439 @@ def remove_creator_from_private_pool(request): }, json_dumps_params={'ensure_ascii': False}) +@api_view(['POST']) +@authentication_classes([CustomTokenAuthentication]) +@csrf_exempt +@require_http_methods(["POST"]) +def filter_public_creators(request): + """根据过滤条件筛选公有达人库列表""" + try: + from .models import PublicCreatorPool, CreatorProfile + import json + from django.db.models import Q + + # 从URL获取分页参数 + page = int(request.GET.get('page', 1)) + page_size = int(request.GET.get('page_size', 10)) + + # 解析POST请求体 + data = json.loads(request.body) + filter_data = data.get('filter', {}) + + # 基础查询 - 从公有达人池开始 + public_creators = PublicCreatorPool.objects.all() + + # 达人分类过滤(Category 多选过滤) + category = filter_data.get('category') + if category and len(category) > 0: + public_creators = public_creators.filter(creator__category__in=category) + + # 电商能力等级过滤 (L1-L7),多选 + e_commerce_level = filter_data.get('e_commerce_level') + if e_commerce_level and len(e_commerce_level) > 0: + level_nums = [] + for level_str in e_commerce_level: + if level_str.startswith('L'): + level_nums.append(int(level_str[1:])) + if level_nums: + public_creators = public_creators.filter(creator__e_commerce_level__in=level_nums) + + # 曝光等级过滤 (KOL-1, KOL-2, KOC-1等),多选 + exposure_level = filter_data.get('exposure_level') + if exposure_level and len(exposure_level) > 0: + public_creators = public_creators.filter(creator__exposure_level__in=exposure_level) + + # GMV范围过滤 ($0-$5k, $5k-$25k, $25k-$50k等),多选 + gmv_range = filter_data.get('gmv_range') + if gmv_range and len(gmv_range) > 0: + gmv_q = Q() + for gmv_val in gmv_range: + gmv_min, gmv_max = 0, float('inf') + if gmv_val == "$0-$5k": + gmv_min, gmv_max = 0, 5 + elif gmv_val == "$5k-$25k": + gmv_min, gmv_max = 5, 25 + elif gmv_val == "$25k-$50k": + gmv_min, gmv_max = 25, 50 + elif gmv_val == "$50k-$150k": + gmv_min, gmv_max = 50, 150 + elif gmv_val == "$150k-$400k": + gmv_min, gmv_max = 150, 400 + elif gmv_val == "$400k-$1500k": + gmv_min, gmv_max = 400, 1500 + elif gmv_val == "$1500k+": + gmv_min, gmv_max = 1500, float('inf') + + range_q = Q() + if gmv_min > 0: + range_q &= Q(creator__gmv__gte=gmv_min) + if gmv_max < float('inf'): + range_q &= Q(creator__gmv__lte=gmv_max) + gmv_q |= range_q + + public_creators = public_creators.filter(gmv_q) + + # 观看量范围过滤,单选 + views_range = filter_data.get('views_range') + if views_range and len(views_range) > 0: + views_min, views_max = 0, float('inf') + views_val = views_range[0] + if views_val == "0-100": + views_min, views_max = 0, 100 + elif views_val == "1k-10k": + views_min, views_max = 1000, 10000 + elif views_val == "10k-100k": + views_min, views_max = 10000, 100000 + elif views_val == "100k-250k": + views_min, views_max = 100000, 250000 + elif views_val == "250k-500k": + views_min, views_max = 250000, 500000 + elif views_val == "500k+": + views_min = 500000 + + if views_min > 0: + public_creators = public_creators.filter(creator__avg_video_views__gte=views_min) + if views_max < float('inf'): + public_creators = public_creators.filter(creator__avg_video_views__lte=views_max) + + # 价格区间过滤逻辑,单选 + pricing = filter_data.get('pricing') + if pricing and len(pricing) > 0: + pricing_val = pricing[0] + if '-' in pricing_val: + min_price, max_price = pricing_val.split('-') + min_price = float(min_price) + max_price = float(max_price) + # 修改:根据单一定价字段判断是否在区间内 + public_creators = public_creators.filter( + creator__pricing__gte=min_price, + creator__pricing__lte=max_price + ) + + # 获取总数据量 + total_count = public_creators.count() + + # 计算分页 + start = (page - 1) * page_size + end = start + page_size + + # 执行查询并分页 + creators = public_creators[start:end] + + creator_list = [] + for public_creator in creators: + creator = public_creator.creator + + # 格式化电商等级 + e_commerce_level_formatted = f"L{creator.e_commerce_level}" if creator.e_commerce_level else None + + # 格式化GMV + gmv_formatted = f"${creator.gmv}k" if creator.gmv else "$0" + + # 格式化粉丝数和观看量 + followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0" + avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0" + + # 格式化价格区间 + if creator.pricing is not None: + pricing_range = f"${creator.pricing}" + else: + pricing_range = None + + # 格式化结果 + formatted_creator = { + "public_id": public_creator.id, + "creator_id": creator.id, + "name": creator.name, + "avatar": creator.avatar_url, + "category": creator.category, + "e_commerce_level": e_commerce_level_formatted, + "exposure_level": creator.exposure_level, + "followers": followers_formatted, + "gmv": gmv_formatted, + "avg_video_views": avg_views_formatted, + "pricing": pricing_range, + "pricing_package": creator.pricing_package, + "collab_count": creator.collab_count, + "remark": public_creator.remark, + "category_public": public_creator.category + } + creator_list.append(formatted_creator) + + # 计算总页数 + total_pages = (total_count + page_size - 1) // page_size + + # 构造分页信息 + pagination = { + "current_page": page, + "total_pages": total_pages, + "total_count": total_count, + "has_next": page < total_pages, + "has_prev": page > 1 + } + + return JsonResponse({ + 'code': 200, + 'message': '获取成功', + 'data': creator_list, + 'pagination': pagination + }, json_dumps_params={'ensure_ascii': False}) + + except Exception as e: + logger.error(f"筛选公有达人库列表失败: {e}") + import traceback + logger.error(f"详细错误: {traceback.format_exc()}") + return JsonResponse({ + 'code': 500, + 'message': f'筛选公有达人库列表失败: {str(e)}', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) + + +@api_view(['POST']) +@authentication_classes([CustomTokenAuthentication]) +@csrf_exempt +@require_http_methods(["POST"]) +def filter_private_pool_creators(request): + """根据过滤条件筛选私有达人库中的达人""" + try: + from .models import PrivateCreatorPool, PrivateCreatorRelation + import json + from django.db.models import Q + + # 解析POST请求体 + data = json.loads(request.body) + + # 获取私有库ID + pool_id = data.get('pool_id') + if not pool_id: + return JsonResponse({ + 'code': 400, + 'message': '缺少必要参数: pool_id', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) + + # 获取过滤条件 + filter_data = data.get('filter', {}) + + # 获取分页参数 + page = int(request.GET.get('page', 1)) + page_size = int(request.GET.get('page_size', 10)) + + # 获取状态过滤参数,如果提供了才使用 + status = filter_data.get('status') + + # 查询私有库信息 + try: + private_pool = PrivateCreatorPool.objects.get(id=pool_id) + + # 检查私有库是否属于当前登录用户 + if private_pool.user_id != request.user.id: + return JsonResponse({ + 'code': 403, + 'message': '没有权限访问此私有达人库', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) + + except PrivateCreatorPool.DoesNotExist: + return JsonResponse({ + 'code': 404, + 'message': f'找不到ID为 {pool_id} 的私有库', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) + + # 查询私有库中的达人关联 + creator_relations = PrivateCreatorRelation.objects.filter( + private_pool=private_pool + ).select_related('creator') + + # 应用状态过滤条件(仅当提供了status参数时) + if status: + creator_relations = creator_relations.filter(status=status) + + # 应用复杂过滤条件 + # --------- 从filter_creators借鉴的过滤逻辑 --------- + + # Category 多选过滤 + category = filter_data.get('category') + if category and len(category) > 0: + creator_relations = creator_relations.filter(creator__category__in=category) + + # 电商能力等级过滤 (L1-L7),多选 + e_commerce_level = filter_data.get('e_commerce_level') + if e_commerce_level and len(e_commerce_level) > 0: + level_nums = [] + for level_str in e_commerce_level: + if level_str.startswith('L'): + level_nums.append(int(level_str[1:])) + if level_nums: + creator_relations = creator_relations.filter(creator__e_commerce_level__in=level_nums) + + # 曝光等级过滤 (KOL-1, KOL-2, KOC-1等),多选 + exposure_level = filter_data.get('exposure_level') + if exposure_level and len(exposure_level) > 0: + creator_relations = creator_relations.filter(creator__exposure_level__in=exposure_level) + + # GMV范围过滤 ($0-$5k, $5k-$25k, $25k-$50k等),多选 + gmv_range = filter_data.get('gmv_range') + if gmv_range and len(gmv_range) > 0: + gmv_q = Q() + for gmv_val in gmv_range: + gmv_min, gmv_max = 0, float('inf') + if gmv_val == "$0-$5k": + gmv_min, gmv_max = 0, 5 + elif gmv_val == "$5k-$25k": + gmv_min, gmv_max = 5, 25 + elif gmv_val == "$25k-$50k": + gmv_min, gmv_max = 25, 50 + elif gmv_val == "$50k-$150k": + gmv_min, gmv_max = 50, 150 + elif gmv_val == "$150k-$400k": + gmv_min, gmv_max = 150, 400 + elif gmv_val == "$400k-$1500k": + gmv_min, gmv_max = 400, 1500 + elif gmv_val == "$1500k+": + gmv_min, gmv_max = 1500, float('inf') + + range_q = Q() + if gmv_min > 0: + range_q &= Q(creator__gmv__gte=gmv_min) + if gmv_max < float('inf'): + range_q &= Q(creator__gmv__lte=gmv_max) + gmv_q |= range_q + + creator_relations = creator_relations.filter(gmv_q) + + # 观看量范围过滤,单选 + views_range = filter_data.get('views_range') + if views_range and len(views_range) > 0: + views_min, views_max = 0, float('inf') + views_val = views_range[0] + if views_val == "0-100": + views_min, views_max = 0, 100 + elif views_val == "1k-10k": + views_min, views_max = 1000, 10000 + elif views_val == "10k-100k": + views_min, views_max = 10000, 100000 + elif views_val == "100k-250k": + views_min, views_max = 100000, 250000 + elif views_val == "250k-500k": + views_min, views_max = 250000, 500000 + elif views_val == "500k+": + views_min = 500000 + + if views_min > 0: + creator_relations = creator_relations.filter(creator__avg_video_views__gte=views_min) + if views_max < float('inf'): + creator_relations = creator_relations.filter(creator__avg_video_views__lte=views_max) + + # 价格区间过滤逻辑,单选 + pricing = filter_data.get('pricing') + if pricing and len(pricing) > 0: + pricing_val = pricing[0] + if '-' in pricing_val: + min_price, max_price = pricing_val.split('-') + min_price = float(min_price) + max_price = float(max_price) + # 修改:根据单一定价字段判断是否在区间内 + creator_relations = creator_relations.filter( + creator__pricing__gte=min_price, + creator__pricing__lte=max_price + ) + + # 获取总数据量 + total_count = creator_relations.count() + + # 计算分页 + start = (page - 1) * page_size + end = start + page_size + + # 执行查询并分页 + paged_relations = creator_relations[start:end] + + creator_list = [] + for relation in paged_relations: + creator = relation.creator + + # 格式化电商等级 + e_commerce_level_formatted = f"L{creator.e_commerce_level}" if creator.e_commerce_level else None + + # 格式化GMV + gmv_formatted = f"${creator.gmv}k" if creator.gmv else "$0" + + # 格式化粉丝数和观看量 + followers_formatted = f"{int(creator.followers / 1000)}k" if creator.followers else "0" + avg_views_formatted = f"{int(creator.avg_video_views / 1000)}k" if creator.avg_video_views else "0" + + # 格式化价格区间 + if creator.pricing is not None: + pricing_range = f"${creator.pricing}" + else: + pricing_range = None + + # 格式化结果 + formatted_creator = { + "relation_id": relation.id, + "creator_id": creator.id, + "name": creator.name, + "avatar": creator.avatar_url, + "category": creator.category, + "e_commerce_level": e_commerce_level_formatted, + "exposure_level": creator.exposure_level, + "followers": followers_formatted, + "gmv": gmv_formatted, + "avg_video_views": avg_views_formatted, + "pricing": pricing_range, # 使用格式化后的价格区间 + "pricing_package": creator.pricing_package, + "collab_count": creator.collab_count, + "notes": relation.notes, + "status": relation.status, + "added_from_public": relation.added_from_public, + "added_at": relation.created_at.strftime('%Y-%m-%d') + } + creator_list.append(formatted_creator) + + # 计算总页数 + total_pages = (total_count + page_size - 1) // page_size + + # 构造分页信息 + pagination = { + "current_page": page, + "total_pages": total_pages, + "total_count": total_count, + "has_next": page < total_pages, + "has_prev": page > 1 + } + + # 构造私有库信息 + pool_info = { + "id": private_pool.id, + "name": private_pool.name, + "description": private_pool.description, + "is_default": private_pool.is_default, + "user_id": private_pool.user_id, + "created_at": private_pool.created_at.strftime('%Y-%m-%d') + } + + return JsonResponse({ + 'code': 200, + 'message': '获取成功', + 'data': creator_list, + 'pagination': pagination, + 'pool_info': pool_info + }, json_dumps_params={'ensure_ascii': False}) + + except Exception as e: + logger.error(f"筛选私有达人库列表失败: {e}") + import traceback + logger.error(f"详细错误: {traceback.format_exc()}") + return JsonResponse({ + 'code': 500, + 'message': f'筛选私有达人库列表失败: {str(e)}', + 'data': None + }, json_dumps_params={'ensure_ascii': False}) + + diff --git a/apps/user/authentication.py b/apps/user/authentication.py index 9c2bf2c..602b8ca 100644 --- a/apps/user/authentication.py +++ b/apps/user/authentication.py @@ -3,6 +3,14 @@ from rest_framework import exceptions from django.contrib.auth.models import AnonymousUser from .models import User, UserToken from django.utils import timezone +from rest_framework.exceptions import APIException +from rest_framework import status + +class CustomAuthenticationFailed(APIException): + status_code = status.HTTP_401_UNAUTHORIZED + + def __init__(self, code, message): + self.detail = {"code": code, "message": message} class CustomTokenAuthentication(authentication.BaseAuthentication): keyword = 'Token' # 设置认证头关键字 @@ -12,14 +20,14 @@ class CustomTokenAuthentication(authentication.BaseAuthentication): auth_header = request.META.get('HTTP_AUTHORIZATION') if not auth_header: - raise exceptions.AuthenticationFailed('未提供认证头') + raise CustomAuthenticationFailed(401, '未提供认证头') try: # 提取token parts = auth_header.split() if len(parts) != 2 or parts[0] != self.keyword: - raise exceptions.AuthenticationFailed('认证头格式不正确') + raise CustomAuthenticationFailed(401, '认证头格式不正确') token = parts[1] @@ -30,18 +38,18 @@ class CustomTokenAuthentication(authentication.BaseAuthentication): expired_at__gt=timezone.now() # 确保token未过期 ) except UserToken.DoesNotExist: - raise exceptions.AuthenticationFailed('无效或过期的token') + raise CustomAuthenticationFailed(401, '无效或过期的token') # 检查用户是否激活 if not token_obj.user.is_active: - raise exceptions.AuthenticationFailed('用户未激活') + raise CustomAuthenticationFailed(401, '用户未激活') return (token_obj.user, None) except Exception as e: - if isinstance(e, exceptions.AuthenticationFailed): + if isinstance(e, CustomAuthenticationFailed): raise e - raise exceptions.AuthenticationFailed('认证失败') + raise CustomAuthenticationFailed(401, '认证失败') def authenticate_header(self, request): return self.keyword \ No newline at end of file