From f220c59c5a816ae981dcc324de100e09115a02d5 Mon Sep 17 00:00:00 2001 From: wanjia Date: Thu, 22 May 2025 15:26:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E4=B8=8D=E8=A6=81=E7=9A=84?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/brands/__init__.py | 0 apps/brands/admin.py | 96 ----- apps/brands/apps.py | 6 - apps/brands/migrations/0001_initial.py | 99 ------ ...alter_activity_unique_together_and_more.py | 194 ----------- ...brands_bran_brand_i_d22614_idx_and_more.py | 89 ----- apps/brands/migrations/__init__.py | 3 - apps/brands/models.py | 177 ---------- apps/brands/serializers.py | 67 ---- apps/brands/services/__init__.py | 1 - apps/brands/tests.py | 3 - apps/brands/urls.py | 18 - apps/brands/views.py | 317 ----------------- apps/discovery/README.md | 329 ------------------ apps/discovery/__init__.py | 3 - apps/discovery/admin.py | 18 - apps/discovery/apps.py | 7 - apps/discovery/exceptions.py | 31 -- apps/discovery/management/__init__.py | 3 - .../discovery/management/commands/__init__.py | 3 - .../commands/create_mock_discovery_data.py | 73 ---- apps/discovery/migrations/0001_initial.py | 57 --- .../0002_alter_searchsession_date_created.py | 19 - apps/discovery/migrations/__init__.py | 0 apps/discovery/models.py | 79 ----- apps/discovery/pagination.py | 22 -- apps/discovery/serializers.py | 35 -- apps/discovery/tests.py | 3 - apps/discovery/urls.py | 12 - apps/discovery/views.py | 276 --------------- apps/template/admin.py | 0 apps/template/apps.py | 7 - apps/template/exceptions.py | 59 ---- apps/template/filters.py | 26 -- apps/template/migrations/0001_initial.py | 53 --- .../0002_remove_template_created_by.py | 17 - apps/template/migrations/__init__.py | 0 apps/template/models.py | 77 ---- apps/template/pagination.py | 37 -- apps/template/serializers.py | 104 ------ apps/template/tests.py | 3 - apps/template/urls.py | 11 - apps/template/utils.py | 36 -- apps/template/views.py | 297 ---------------- daren_project/settings.py | 3 - daren_project/urls.py | 3 - 46 files changed, 2773 deletions(-) delete mode 100644 apps/brands/__init__.py delete mode 100644 apps/brands/admin.py delete mode 100644 apps/brands/apps.py delete mode 100644 apps/brands/migrations/0001_initial.py delete mode 100644 apps/brands/migrations/0002_campaign_alter_activity_unique_together_and_more.py delete mode 100644 apps/brands/migrations/0003_rename_brand_chat__brand_i_83752e_idx_brands_bran_brand_i_d22614_idx_and_more.py delete mode 100644 apps/brands/migrations/__init__.py delete mode 100644 apps/brands/models.py delete mode 100644 apps/brands/serializers.py delete mode 100644 apps/brands/services/__init__.py delete mode 100644 apps/brands/tests.py delete mode 100644 apps/brands/urls.py delete mode 100644 apps/brands/views.py delete mode 100644 apps/discovery/README.md delete mode 100644 apps/discovery/__init__.py delete mode 100644 apps/discovery/admin.py delete mode 100644 apps/discovery/apps.py delete mode 100644 apps/discovery/exceptions.py delete mode 100644 apps/discovery/management/__init__.py delete mode 100644 apps/discovery/management/commands/__init__.py delete mode 100644 apps/discovery/management/commands/create_mock_discovery_data.py delete mode 100644 apps/discovery/migrations/0001_initial.py delete mode 100644 apps/discovery/migrations/0002_alter_searchsession_date_created.py delete mode 100644 apps/discovery/migrations/__init__.py delete mode 100644 apps/discovery/models.py delete mode 100644 apps/discovery/pagination.py delete mode 100644 apps/discovery/serializers.py delete mode 100644 apps/discovery/tests.py delete mode 100644 apps/discovery/urls.py delete mode 100644 apps/discovery/views.py delete mode 100644 apps/template/admin.py delete mode 100644 apps/template/apps.py delete mode 100644 apps/template/exceptions.py delete mode 100644 apps/template/filters.py delete mode 100644 apps/template/migrations/0001_initial.py delete mode 100644 apps/template/migrations/0002_remove_template_created_by.py delete mode 100644 apps/template/migrations/__init__.py delete mode 100644 apps/template/models.py delete mode 100644 apps/template/pagination.py delete mode 100644 apps/template/serializers.py delete mode 100644 apps/template/tests.py delete mode 100644 apps/template/urls.py delete mode 100644 apps/template/utils.py delete mode 100644 apps/template/views.py diff --git a/apps/brands/__init__.py b/apps/brands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/brands/admin.py b/apps/brands/admin.py deleted file mode 100644 index 3b3c106..0000000 --- a/apps/brands/admin.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.contrib import admin -from .models import Brand, Product, Campaign, BrandChatSession - -@admin.register(Brand) -class BrandAdmin(admin.ModelAdmin): - list_display = ('name', 'category', 'source', 'collab_count', 'creators_count', 'total_gmv_achieved', 'total_views_achieved', 'shop_overall_rating', 'created_at', 'is_active') - search_fields = ('name', 'description', 'category', 'source') - list_filter = ('is_active', 'created_at', 'category', 'source') - readonly_fields = ('id', 'created_at', 'updated_at') - fieldsets = ( - ('基本信息', { - 'fields': ('id', 'name', 'description', 'logo_url', 'is_active') - }), - ('分类信息', { - 'fields': ('category', 'source', 'collab_count', 'creators_count', 'campaign_id') - }), - ('统计信息', { - 'fields': ('total_gmv_achieved', 'total_views_achieved', 'shop_overall_rating') - }), - ('知识库关联', { - 'fields': ('dataset_id_list',) - }), - ('时间信息', { - 'fields': ('created_at', 'updated_at') - }), - ) - -@admin.register(Product) -class ProductAdmin(admin.ModelAdmin): - list_display = ('name', 'brand', 'pid', 'commission_rate', 'stock', 'items_sold', 'product_rating', 'created_at', 'is_active') - search_fields = ('name', 'description', 'brand__name', 'pid') - list_filter = ('brand', 'is_active', 'created_at', 'tiktok_shop') - readonly_fields = ('id', 'created_at', 'updated_at') - fieldsets = ( - ('基本信息', { - 'fields': ('id', 'name', 'brand', 'description', 'image_url', 'is_active') - }), - ('产品详情', { - 'fields': ('pid', 'commission_rate', 'open_collab', 'available_samples', - 'sales_price_min', 'sales_price_max', 'stock', 'items_sold', - 'product_rating', 'reviews_count', 'collab_creators', 'tiktok_shop') - }), - ('知识库信息', { - 'fields': ('dataset_id', 'external_id') - }), - ('时间信息', { - 'fields': ('created_at', 'updated_at') - }), - ) - -@admin.register(Campaign) -class CampaignAdmin(admin.ModelAdmin): - list_display = ('name', 'brand', 'service', 'creator_type', 'start_date', 'end_date', 'is_active') - search_fields = ('name', 'description', 'brand__name', 'service', 'creator_type') - list_filter = ('brand', 'is_active', 'start_date', 'end_date', 'service', 'creator_type') - readonly_fields = ('id', 'created_at', 'updated_at') - filter_horizontal = ('link_product',) - fieldsets = ( - ('基本信息', { - 'fields': ('id', 'name', 'brand', 'description', 'image_url', 'is_active') - }), - ('活动详情', { - 'fields': ('service', 'creator_type', 'creator_level', 'creator_category', - 'creators_count', 'gmv', 'followers', 'views', 'budget') - }), - ('关联产品', { - 'fields': ('link_product',) - }), - ('活动时间', { - 'fields': ('start_date', 'end_date') - }), - ('知识库信息', { - 'fields': ('dataset_id', 'external_id') - }), - ('时间信息', { - 'fields': ('created_at', 'updated_at') - }), - ) - -@admin.register(BrandChatSession) -class BrandChatSessionAdmin(admin.ModelAdmin): - list_display = ('title', 'brand', 'session_id', 'created_at', 'is_active') - search_fields = ('title', 'session_id', 'brand__name') - list_filter = ('brand', 'is_active', 'created_at') - readonly_fields = ('id', 'created_at', 'updated_at') - fieldsets = ( - ('基本信息', { - 'fields': ('id', 'title', 'brand', 'session_id', 'is_active') - }), - ('知识库信息', { - 'fields': ('dataset_id_list',) - }), - ('时间信息', { - 'fields': ('created_at', 'updated_at') - }), - ) diff --git a/apps/brands/apps.py b/apps/brands/apps.py deleted file mode 100644 index 24a1db0..0000000 --- a/apps/brands/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class BrandsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.brands' diff --git a/apps/brands/migrations/0001_initial.py b/apps/brands/migrations/0001_initial.py deleted file mode 100644 index c94f925..0000000 --- a/apps/brands/migrations/0001_initial.py +++ /dev/null @@ -1,99 +0,0 @@ -# Generated by Django 5.2 on 2025-05-09 03:55 - -import django.db.models.deletion -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Brand', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, unique=True, verbose_name='品牌名称')), - ('description', models.TextField(blank=True, null=True, verbose_name='品牌描述')), - ('logo_url', models.CharField(blank=True, max_length=255, null=True, verbose_name='品牌Logo')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('is_active', models.BooleanField(default=True, verbose_name='是否激活')), - ('dataset_id_list', models.JSONField(blank=True, default=list, help_text='所有关联的知识库ID列表', verbose_name='知识库ID列表')), - ], - options={ - 'verbose_name': '品牌', - 'verbose_name_plural': '品牌', - 'db_table': 'brands', - }, - ), - migrations.CreateModel( - name='Activity', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, verbose_name='活动名称')), - ('description', models.TextField(blank=True, null=True, verbose_name='活动描述')), - ('image_url', models.CharField(blank=True, max_length=255, null=True, verbose_name='活动图片')), - ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='开始日期')), - ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='结束日期')), - ('dataset_id', models.CharField(help_text='外部知识库系统中的ID', max_length=100, verbose_name='知识库ID')), - ('external_id', models.CharField(blank=True, help_text='外部系统中的唯一标识', max_length=100, null=True, verbose_name='外部ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('is_active', models.BooleanField(default=True, verbose_name='是否激活')), - ('brand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='brands.brand', verbose_name='所属品牌')), - ], - options={ - 'verbose_name': '活动', - 'verbose_name_plural': '活动', - 'db_table': 'activities', - 'indexes': [models.Index(fields=['brand'], name='activities_brand_i_9a57da_idx'), models.Index(fields=['dataset_id'], name='activities_dataset_c873ab_idx'), models.Index(fields=['is_active'], name='activities_is_acti_cff4bc_idx'), models.Index(fields=['start_date'], name='activities_start_d_fe1952_idx'), models.Index(fields=['end_date'], name='activities_end_dat_9cb2d8_idx')], - 'unique_together': {('brand', 'name')}, - }, - ), - migrations.CreateModel( - name='BrandChatSession', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('session_id', models.CharField(max_length=100, unique=True, verbose_name='会话ID')), - ('title', models.CharField(default='新对话', max_length=200, verbose_name='会话标题')), - ('dataset_id_list', models.JSONField(blank=True, default=list, verbose_name='知识库ID列表')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('is_active', models.BooleanField(default=True, verbose_name='是否激活')), - ('brand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chat_sessions', to='brands.brand', verbose_name='品牌')), - ], - options={ - 'verbose_name': '品牌聊天会话', - 'verbose_name_plural': '品牌聊天会话', - 'db_table': 'brand_chat_sessions', - 'indexes': [models.Index(fields=['brand'], name='brand_chat__brand_i_83752e_idx'), models.Index(fields=['session_id'], name='brand_chat__session_4bf9b0_idx'), models.Index(fields=['created_at'], name='brand_chat__created_957266_idx')], - }, - ), - migrations.CreateModel( - name='Product', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, verbose_name='产品名称')), - ('description', models.TextField(blank=True, null=True, verbose_name='产品描述')), - ('image_url', models.CharField(blank=True, max_length=255, null=True, verbose_name='产品图片')), - ('dataset_id', models.CharField(help_text='外部知识库系统中的ID', max_length=100, verbose_name='知识库ID')), - ('external_id', models.CharField(blank=True, help_text='外部系统中的唯一标识', max_length=100, null=True, verbose_name='外部ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('is_active', models.BooleanField(default=True, verbose_name='是否激活')), - ('brand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='brands.brand', verbose_name='所属品牌')), - ], - options={ - 'verbose_name': '产品', - 'verbose_name_plural': '产品', - 'db_table': 'products', - 'indexes': [models.Index(fields=['brand'], name='products_brand_i_0d1950_idx'), models.Index(fields=['dataset_id'], name='products_dataset_faf62a_idx'), models.Index(fields=['is_active'], name='products_is_acti_cb485f_idx')], - 'unique_together': {('brand', 'name')}, - }, - ), - ] diff --git a/apps/brands/migrations/0002_campaign_alter_activity_unique_together_and_more.py b/apps/brands/migrations/0002_campaign_alter_activity_unique_together_and_more.py deleted file mode 100644 index 2e0172a..0000000 --- a/apps/brands/migrations/0002_campaign_alter_activity_unique_together_and_more.py +++ /dev/null @@ -1,194 +0,0 @@ -# Generated by Django 5.2 on 2025-05-13 02:45 - -import django.db.models.deletion -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('brands', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Campaign', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=100, verbose_name='活动名称')), - ('description', models.TextField(blank=True, null=True, verbose_name='活动描述')), - ('image_url', models.CharField(blank=True, max_length=255, null=True, verbose_name='活动图片')), - ('service', models.CharField(blank=True, max_length=100, null=True, verbose_name='服务类型')), - ('creator_type', models.CharField(blank=True, max_length=100, null=True, verbose_name='创作者类型')), - ('creator_level', models.CharField(blank=True, max_length=100, null=True, verbose_name='创作者等级')), - ('creator_category', models.CharField(blank=True, max_length=100, null=True, verbose_name='创作者分类')), - ('creators_count', models.IntegerField(default=0, verbose_name='创作者数量')), - ('gmv', models.CharField(blank=True, max_length=100, null=True, verbose_name='GMV范围')), - ('followers', models.CharField(blank=True, max_length=100, null=True, verbose_name='粉丝数范围')), - ('views', models.CharField(blank=True, max_length=100, null=True, verbose_name='浏览量范围')), - ('budget', models.CharField(blank=True, max_length=100, null=True, verbose_name='预算范围')), - ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='开始日期')), - ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='结束日期')), - ('dataset_id', models.CharField(help_text='外部知识库系统中的ID', max_length=100, verbose_name='知识库ID')), - ('external_id', models.CharField(blank=True, help_text='外部系统中的唯一标识', max_length=100, null=True, verbose_name='外部ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('is_active', models.BooleanField(default=True, verbose_name='是否激活')), - ], - options={ - 'verbose_name': '活动', - 'verbose_name_plural': '活动', - 'db_table': 'campaigns', - }, - ), - migrations.AlterUniqueTogether( - name='activity', - unique_together=None, - ), - migrations.RemoveField( - model_name='activity', - name='brand', - ), - migrations.AddField( - model_name='brand', - name='campaign_id', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='活动ID'), - ), - migrations.AddField( - model_name='brand', - name='category', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='品牌分类'), - ), - migrations.AddField( - model_name='brand', - name='collab_count', - field=models.IntegerField(default=0, verbose_name='合作数量'), - ), - migrations.AddField( - model_name='brand', - name='creators_count', - field=models.IntegerField(default=0, verbose_name='创作者数量'), - ), - migrations.AddField( - model_name='brand', - name='shop_overall_rating', - field=models.DecimalField(decimal_places=1, default=0.0, max_digits=3, verbose_name='店铺评分'), - ), - migrations.AddField( - model_name='brand', - name='source', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='来源'), - ), - migrations.AddField( - model_name='brand', - name='total_gmv_achieved', - field=models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='总GMV'), - ), - migrations.AddField( - model_name='brand', - name='total_views_achieved', - field=models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='总浏览量'), - ), - migrations.AddField( - model_name='product', - name='available_samples', - field=models.IntegerField(default=0, verbose_name='可用样品数'), - ), - migrations.AddField( - model_name='product', - name='collab_creators', - field=models.IntegerField(default=0, verbose_name='合作创作者数'), - ), - migrations.AddField( - model_name='product', - name='commission_rate', - field=models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='佣金率'), - ), - migrations.AddField( - model_name='product', - name='items_sold', - field=models.IntegerField(default=0, verbose_name='已售数量'), - ), - migrations.AddField( - model_name='product', - name='open_collab', - field=models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='开放合作率'), - ), - migrations.AddField( - model_name='product', - name='pid', - field=models.CharField(blank=True, max_length=100, null=True, verbose_name='产品ID'), - ), - migrations.AddField( - model_name='product', - name='product_rating', - field=models.DecimalField(decimal_places=1, default=0, max_digits=3, verbose_name='产品评分'), - ), - migrations.AddField( - model_name='product', - name='reviews_count', - field=models.IntegerField(default=0, verbose_name='评价数量'), - ), - migrations.AddField( - model_name='product', - name='sales_price_max', - field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='最高销售价'), - ), - migrations.AddField( - model_name='product', - name='sales_price_min', - field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='最低销售价'), - ), - migrations.AddField( - model_name='product', - name='stock', - field=models.IntegerField(default=0, verbose_name='库存'), - ), - migrations.AddField( - model_name='product', - name='tiktok_shop', - field=models.BooleanField(default=False, verbose_name='是否TikTok商店'), - ), - migrations.AddIndex( - model_name='product', - index=models.Index(fields=['pid'], name='products_pid_99aab2_idx'), - ), - migrations.AddField( - model_name='campaign', - name='brand', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='campaigns', to='brands.brand', verbose_name='所属品牌'), - ), - migrations.AddField( - model_name='campaign', - name='link_product', - field=models.ManyToManyField(blank=True, related_name='campaigns', to='brands.product', verbose_name='关联产品'), - ), - migrations.DeleteModel( - name='Activity', - ), - migrations.AddIndex( - model_name='campaign', - index=models.Index(fields=['brand'], name='campaigns_brand_i_c2d4bd_idx'), - ), - migrations.AddIndex( - model_name='campaign', - index=models.Index(fields=['dataset_id'], name='campaigns_dataset_bfbb68_idx'), - ), - migrations.AddIndex( - model_name='campaign', - index=models.Index(fields=['is_active'], name='campaigns_is_acti_6c57d0_idx'), - ), - migrations.AddIndex( - model_name='campaign', - index=models.Index(fields=['start_date'], name='campaigns_start_d_5c2c6b_idx'), - ), - migrations.AddIndex( - model_name='campaign', - index=models.Index(fields=['end_date'], name='campaigns_end_dat_6aaba4_idx'), - ), - migrations.AlterUniqueTogether( - name='campaign', - unique_together={('brand', 'name')}, - ), - ] diff --git a/apps/brands/migrations/0003_rename_brand_chat__brand_i_83752e_idx_brands_bran_brand_i_d22614_idx_and_more.py b/apps/brands/migrations/0003_rename_brand_chat__brand_i_83752e_idx_brands_bran_brand_i_d22614_idx_and_more.py deleted file mode 100644 index 3ae62bc..0000000 --- a/apps/brands/migrations/0003_rename_brand_chat__brand_i_83752e_idx_brands_bran_brand_i_d22614_idx_and_more.py +++ /dev/null @@ -1,89 +0,0 @@ -# Generated by Django 5.2 on 2025-05-21 04:30 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('brands', '0002_campaign_alter_activity_unique_together_and_more'), - ] - - operations = [ - migrations.RenameIndex( - model_name='brandchatsession', - new_name='brands_bran_brand_i_d22614_idx', - old_name='brand_chat__brand_i_83752e_idx', - ), - migrations.RenameIndex( - model_name='brandchatsession', - new_name='brands_bran_session_574261_idx', - old_name='brand_chat__session_4bf9b0_idx', - ), - migrations.RenameIndex( - model_name='brandchatsession', - new_name='brands_bran_created_c31416_idx', - old_name='brand_chat__created_957266_idx', - ), - migrations.RenameIndex( - model_name='campaign', - new_name='brands_camp_brand_i_51f26d_idx', - old_name='campaigns_brand_i_c2d4bd_idx', - ), - migrations.RenameIndex( - model_name='campaign', - new_name='brands_camp_dataset_9b31e4_idx', - old_name='campaigns_dataset_bfbb68_idx', - ), - migrations.RenameIndex( - model_name='campaign', - new_name='brands_camp_is_acti_c09fdb_idx', - old_name='campaigns_is_acti_6c57d0_idx', - ), - migrations.RenameIndex( - model_name='campaign', - new_name='brands_camp_start_d_42e603_idx', - old_name='campaigns_start_d_5c2c6b_idx', - ), - migrations.RenameIndex( - model_name='campaign', - new_name='brands_camp_end_dat_97d26b_idx', - old_name='campaigns_end_dat_6aaba4_idx', - ), - migrations.RenameIndex( - model_name='product', - new_name='brands_prod_brand_i_e0821d_idx', - old_name='products_brand_i_0d1950_idx', - ), - migrations.RenameIndex( - model_name='product', - new_name='brands_prod_dataset_c7f534_idx', - old_name='products_dataset_faf62a_idx', - ), - migrations.RenameIndex( - model_name='product', - new_name='brands_prod_is_acti_fd82c6_idx', - old_name='products_is_acti_cb485f_idx', - ), - migrations.RenameIndex( - model_name='product', - new_name='brands_prod_pid_452ccb_idx', - old_name='products_pid_99aab2_idx', - ), - migrations.AlterModelTable( - name='brand', - table=None, - ), - migrations.AlterModelTable( - name='brandchatsession', - table=None, - ), - migrations.AlterModelTable( - name='campaign', - table=None, - ), - migrations.AlterModelTable( - name='product', - table=None, - ), - ] diff --git a/apps/brands/migrations/__init__.py b/apps/brands/migrations/__init__.py deleted file mode 100644 index b28b04f..0000000 --- a/apps/brands/migrations/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/brands/models.py b/apps/brands/models.py deleted file mode 100644 index 8bb638f..0000000 --- a/apps/brands/models.py +++ /dev/null @@ -1,177 +0,0 @@ -from django.db import models -import uuid -from django.utils import timezone - -class Brand(models.Model): - """品牌模型""" - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField(max_length=100, unique=True, verbose_name='品牌名称') - description = models.TextField(blank=True, null=True, verbose_name='品牌描述') - logo_url = models.CharField(max_length=255, blank=True, null=True, verbose_name='品牌Logo') - category = models.CharField(max_length=100, blank=True, null=True, verbose_name='品牌分类') - source = models.CharField(max_length=100, blank=True, null=True, verbose_name='来源') - collab_count = models.IntegerField(default=0, verbose_name='合作数量') - creators_count = models.IntegerField(default=0, verbose_name='创作者数量') - campaign_id = models.CharField(max_length=100, blank=True, null=True, verbose_name='活动ID') - - # 添加数据统计字段 - total_gmv_achieved = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='总GMV') - total_views_achieved = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='总浏览量') - shop_overall_rating = models.DecimalField(max_digits=3, decimal_places=1, default=0.0, verbose_name='店铺评分') - - # 存储关联到此品牌的所有产品和活动知识库ID列表 - dataset_id_list = models.JSONField(default=list, blank=True, verbose_name='知识库ID列表', - help_text='所有关联的知识库ID列表') - created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') - is_active = models.BooleanField(default=True, verbose_name='是否激活') - - class Meta: - verbose_name = '品牌' - verbose_name_plural = '品牌' - - def __str__(self): - return self.name - - -class Product(models.Model): - """产品模型 - 作为一个知识库""" - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name='products', verbose_name='所属品牌') - name = models.CharField(max_length=100, verbose_name='产品名称') - description = models.TextField(blank=True, null=True, verbose_name='产品描述') - image_url = models.CharField(max_length=255, blank=True, null=True, verbose_name='产品图片') - - # 添加产品详情字段 - pid = models.CharField(max_length=100, blank=True, null=True, verbose_name='产品ID') - commission_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0, verbose_name='佣金率') - open_collab = models.DecimalField(max_digits=5, decimal_places=2, default=0, verbose_name='开放合作率') - available_samples = models.IntegerField(default=0, verbose_name='可用样品数') - sales_price_min = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='最低销售价') - sales_price_max = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='最高销售价') - stock = models.IntegerField(default=0, verbose_name='库存') - items_sold = models.IntegerField(default=0, verbose_name='已售数量') - product_rating = models.DecimalField(max_digits=3, decimal_places=1, default=0, verbose_name='产品评分') - reviews_count = models.IntegerField(default=0, verbose_name='评价数量') - collab_creators = models.IntegerField(default=0, verbose_name='合作创作者数') - tiktok_shop = models.BooleanField(default=False, verbose_name='是否TikTok商店') - - dataset_id = models.CharField(max_length=100, verbose_name='知识库ID', - help_text='外部知识库系统中的ID') - external_id = models.CharField(max_length=100, blank=True, null=True, verbose_name='外部ID', - help_text='外部系统中的唯一标识') - created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') - is_active = models.BooleanField(default=True, verbose_name='是否激活') - - class Meta: - verbose_name = '产品' - verbose_name_plural = '产品' - unique_together = ['brand', 'name'] - indexes = [ - models.Index(fields=['brand']), - models.Index(fields=['dataset_id']), - models.Index(fields=['is_active']), - models.Index(fields=['pid']), - ] - - def __str__(self): - return f"{self.brand.name} - {self.name}" - - def save(self, *args, **kwargs): - """重写save方法,更新品牌的dataset_id_list""" - is_new = self.pk is None - super().save(*args, **kwargs) - - # 刷新品牌的dataset_id_list - if is_new and self.is_active and self.dataset_id: - brand = self.brand - if self.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(self.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - -class Campaign(models.Model): - """活动模型 - 作为一个知识库""" - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name='campaigns', verbose_name='所属品牌') - name = models.CharField(max_length=100, verbose_name='活动名称') - description = models.TextField(blank=True, null=True, verbose_name='活动描述') - image_url = models.CharField(max_length=255, blank=True, null=True, verbose_name='活动图片') - - # 活动相关字段 - service = models.CharField(max_length=100, blank=True, null=True, verbose_name='服务类型') - creator_type = models.CharField(max_length=100, blank=True, null=True, verbose_name='创作者类型') - creator_level = models.CharField(max_length=100, blank=True, null=True, verbose_name='创作者等级') - creator_category = models.CharField(max_length=100, blank=True, null=True, verbose_name='创作者分类') - creators_count = models.IntegerField(default=0, verbose_name='创作者数量') - gmv = models.CharField(max_length=100, blank=True, null=True, verbose_name='GMV范围') - followers = models.CharField(max_length=100, blank=True, null=True, verbose_name='粉丝数范围') - views = models.CharField(max_length=100, blank=True, null=True, verbose_name='浏览量范围') - budget = models.CharField(max_length=100, blank=True, null=True, verbose_name='预算范围') - link_product = models.ManyToManyField(Product, blank=True, related_name='campaigns', verbose_name='关联产品') - - # 时间信息 - start_date = models.DateTimeField(blank=True, null=True, verbose_name='开始日期') - end_date = models.DateTimeField(blank=True, null=True, verbose_name='结束日期') - - # 知识库信息 - dataset_id = models.CharField(max_length=100, verbose_name='知识库ID', - help_text='外部知识库系统中的ID') - external_id = models.CharField(max_length=100, blank=True, null=True, verbose_name='外部ID', - help_text='外部系统中的唯一标识') - created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') - is_active = models.BooleanField(default=True, verbose_name='是否激活') - - class Meta: - verbose_name = '活动' - verbose_name_plural = '活动' - unique_together = ['brand', 'name'] - indexes = [ - models.Index(fields=['brand']), - models.Index(fields=['dataset_id']), - models.Index(fields=['is_active']), - models.Index(fields=['start_date']), - models.Index(fields=['end_date']), - ] - - def __str__(self): - return f"{self.brand.name} - {self.name}" - - def save(self, *args, **kwargs): - """重写save方法,更新品牌的dataset_id_list""" - is_new = self.pk is None - super().save(*args, **kwargs) - - # 刷新品牌的dataset_id_list - if is_new and self.is_active and self.dataset_id: - brand = self.brand - if self.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(self.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - -class BrandChatSession(models.Model): - """品牌聊天会话模型""" - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name='chat_sessions', verbose_name='品牌') - session_id = models.CharField(max_length=100, unique=True, verbose_name='会话ID') - title = models.CharField(max_length=200, default='新对话', verbose_name='会话标题') - # 存储此次会话使用的所有知识库ID - dataset_id_list = models.JSONField(default=list, blank=True, verbose_name='知识库ID列表') - created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') - is_active = models.BooleanField(default=True, verbose_name='是否激活') - - class Meta: - verbose_name = '品牌聊天会话' - verbose_name_plural = '品牌聊天会话' - indexes = [ - models.Index(fields=['brand']), - models.Index(fields=['session_id']), - models.Index(fields=['created_at']), - ] - - def __str__(self): - return f"{self.brand.name} - {self.title}" diff --git a/apps/brands/serializers.py b/apps/brands/serializers.py deleted file mode 100644 index 90e0fd0..0000000 --- a/apps/brands/serializers.py +++ /dev/null @@ -1,67 +0,0 @@ -from rest_framework import serializers -from .models import Brand, Product, Campaign, BrandChatSession - -class BrandSerializer(serializers.ModelSerializer): - """品牌序列化器""" - class Meta: - model = Brand - fields = ['id', 'name', 'description', 'logo_url', 'category', 'source', - 'collab_count', 'creators_count', 'campaign_id', 'total_gmv_achieved', - 'total_views_achieved', 'shop_overall_rating', 'dataset_id_list', - 'created_at', 'updated_at', 'is_active'] - read_only_fields = ['id', 'created_at', 'updated_at', 'dataset_id_list'] - - -class ProductSerializer(serializers.ModelSerializer): - """产品序列化器""" - brand_name = serializers.CharField(source='brand.name', read_only=True) - - class Meta: - model = Product - fields = ['id', 'brand', 'brand_name', 'name', 'description', 'image_url', - 'pid', 'commission_rate', 'open_collab', 'available_samples', - 'sales_price_min', 'sales_price_max', 'stock', 'items_sold', - 'product_rating', 'reviews_count', 'collab_creators', 'tiktok_shop', - 'dataset_id', 'external_id', 'created_at', 'updated_at', 'is_active'] - read_only_fields = ['id', 'created_at', 'updated_at'] - - -class CampaignSerializer(serializers.ModelSerializer): - """活动序列化器""" - brand_name = serializers.CharField(source='brand.name', read_only=True) - link_product_details = ProductSerializer(source='link_product', many=True, read_only=True) - - class Meta: - model = Campaign - fields = ['id', 'brand', 'brand_name', 'name', 'description', 'image_url', - 'service', 'creator_type', 'creator_level', 'creator_category', - 'creators_count', 'gmv', 'followers', 'views', 'budget', - 'link_product', 'link_product_details', - 'start_date', 'end_date', 'dataset_id', 'external_id', - 'created_at', 'updated_at', 'is_active'] - read_only_fields = ['id', 'created_at', 'updated_at'] - - -class BrandChatSessionSerializer(serializers.ModelSerializer): - """品牌聊天会话序列化器""" - brand_name = serializers.CharField(source='brand.name', read_only=True) - - class Meta: - model = BrandChatSession - fields = ['id', 'brand', 'brand_name', 'session_id', 'title', - 'dataset_id_list', 'created_at', 'updated_at', 'is_active'] - read_only_fields = ['id', 'created_at', 'updated_at'] - - -class BrandDetailSerializer(serializers.ModelSerializer): - """品牌详情序列化器""" - products = ProductSerializer(many=True, read_only=True) - campaigns = CampaignSerializer(many=True, read_only=True) - - class Meta: - model = Brand - fields = ['id', 'name', 'description', 'logo_url', 'category', 'source', - 'collab_count', 'creators_count', 'campaign_id', 'total_gmv_achieved', - 'total_views_achieved', 'shop_overall_rating', 'dataset_id_list', - 'products', 'campaigns', 'created_at', 'updated_at', 'is_active'] - read_only_fields = ['id', 'created_at', 'updated_at', 'dataset_id_list'] \ No newline at end of file diff --git a/apps/brands/services/__init__.py b/apps/brands/services/__init__.py deleted file mode 100644 index 0519ecb..0000000 --- a/apps/brands/services/__init__.py +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/brands/tests.py b/apps/brands/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/apps/brands/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/brands/urls.py b/apps/brands/urls.py deleted file mode 100644 index 740e70c..0000000 --- a/apps/brands/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.urls import path, include -from rest_framework.routers import DefaultRouter -from .views import ( - BrandViewSet, - ProductViewSet, - CampaignViewSet, - BrandChatSessionViewSet -) - -router = DefaultRouter() -router.register(r'brands', BrandViewSet) -router.register(r'products', ProductViewSet) -router.register(r'campaigns', CampaignViewSet) -router.register(r'chat-sessions', BrandChatSessionViewSet) - -urlpatterns = [ - path('', include(router.urls)), -] \ No newline at end of file diff --git a/apps/brands/views.py b/apps/brands/views.py deleted file mode 100644 index 8ff3603..0000000 --- a/apps/brands/views.py +++ /dev/null @@ -1,317 +0,0 @@ -from django.shortcuts import render, get_object_or_404 -from rest_framework import viewsets, status -from rest_framework.decorators import action -from rest_framework.response import Response - -from .models import Brand, Product, Campaign, BrandChatSession -from .serializers import ( - BrandSerializer, - ProductSerializer, - CampaignSerializer, - BrandChatSessionSerializer, - BrandDetailSerializer -) - -def api_response(code=200, message="成功", data=None): - """统一API响应格式""" - return Response({ - 'code': code, - 'message': message, - 'data': data - }) - -class BrandViewSet(viewsets.ModelViewSet): - """品牌API视图集""" - queryset = Brand.objects.all() - serializer_class = BrandSerializer - - def get_serializer_class(self): - if self.action == 'retrieve': - return BrandDetailSerializer - return BrandSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - serializer = self.get_serializer(queryset, many=True) - return api_response(data=serializer.data) - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="创建失败", data=serializer.errors) - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return api_response(data=serializer.data) - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="更新失败", data=serializer.errors) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - return api_response(message="删除成功", data=None) - - @action(detail=True, methods=['get']) - def products(self, request, pk=None): - """获取品牌下的所有产品""" - brand = self.get_object() - products = Product.objects.filter(brand=brand, is_active=True) - serializer = ProductSerializer(products, many=True) - return api_response(data=serializer.data) - - @action(detail=True, methods=['get']) - def campaigns(self, request, pk=None): - """获取品牌下的所有活动""" - brand = self.get_object() - campaigns = Campaign.objects.filter(brand=brand, is_active=True) - serializer = CampaignSerializer(campaigns, many=True) - return api_response(data=serializer.data) - - @action(detail=True, methods=['get']) - def dataset_ids(self, request, pk=None): - """获取品牌的所有知识库ID""" - brand = self.get_object() - return api_response(data={'dataset_id_list': brand.dataset_id_list}) - - -class ProductViewSet(viewsets.ModelViewSet): - """产品API视图集""" - queryset = Product.objects.filter(is_active=True) - serializer_class = ProductSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - serializer = self.get_serializer(queryset, many=True) - return api_response(data=serializer.data) - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="创建失败", data=serializer.errors) - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return api_response(data=serializer.data) - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="更新失败", data=serializer.errors) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - return api_response(message="删除成功", data=None) - - def perform_create(self, serializer): - # 创建产品时自动更新品牌的dataset_id_list - product = serializer.save() - brand = product.brand - - # 确保dataset_id添加到品牌的dataset_id_list中 - if product.dataset_id and product.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(product.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - def perform_update(self, serializer): - # 获取原始产品信息 - old_product = self.get_object() - old_dataset_id = old_product.dataset_id - - # 保存更新后的产品 - product = serializer.save() - brand = product.brand - - # 从品牌的dataset_id_list中移除旧的dataset_id,添加新的dataset_id - if old_dataset_id in brand.dataset_id_list: - brand.dataset_id_list.remove(old_dataset_id) - - if product.dataset_id and product.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(product.dataset_id) - - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - def perform_destroy(self, instance): - # 软删除产品,并从品牌的dataset_id_list中移除对应的ID - instance.is_active = False - instance.save() - - brand = instance.brand - if instance.dataset_id in brand.dataset_id_list: - brand.dataset_id_list.remove(instance.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - -class CampaignViewSet(viewsets.ModelViewSet): - """活动API视图集""" - queryset = Campaign.objects.filter(is_active=True) - serializer_class = CampaignSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - serializer = self.get_serializer(queryset, many=True) - return api_response(data=serializer.data) - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="创建失败", data=serializer.errors) - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return api_response(data=serializer.data) - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="更新失败", data=serializer.errors) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - return api_response(message="删除成功", data=None) - - def perform_create(self, serializer): - # 创建活动时自动更新品牌的dataset_id_list - campaign = serializer.save() - brand = campaign.brand - - # 确保dataset_id添加到品牌的dataset_id_list中 - if campaign.dataset_id and campaign.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(campaign.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - def perform_update(self, serializer): - # 获取原始活动信息 - old_campaign = self.get_object() - old_dataset_id = old_campaign.dataset_id - - # 保存更新后的活动 - campaign = serializer.save() - brand = campaign.brand - - # 从品牌的dataset_id_list中移除旧的dataset_id,添加新的dataset_id - if old_dataset_id in brand.dataset_id_list: - brand.dataset_id_list.remove(old_dataset_id) - - if campaign.dataset_id and campaign.dataset_id not in brand.dataset_id_list: - brand.dataset_id_list.append(campaign.dataset_id) - - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - def perform_destroy(self, instance): - # 软删除活动,并从品牌的dataset_id_list中移除对应的ID - instance.is_active = False - instance.save() - - brand = instance.brand - if instance.dataset_id in brand.dataset_id_list: - brand.dataset_id_list.remove(instance.dataset_id) - brand.save(update_fields=['dataset_id_list', 'updated_at']) - - @action(detail=True, methods=['post']) - def add_product(self, request, pk=None): - """将产品添加到活动中""" - campaign = self.get_object() - product_id = request.data.get('product_id') - - if not product_id: - return api_response(code=400, message="缺少产品ID", data=None) - - try: - product = Product.objects.get(id=product_id, is_active=True) - campaign.link_product.add(product) - return api_response(message="产品添加成功", data=None) - except Product.DoesNotExist: - return api_response(code=404, message="产品不存在", data=None) - except Exception as e: - return api_response(code=500, message=f"添加产品失败: {str(e)}", data=None) - - @action(detail=True, methods=['post']) - def remove_product(self, request, pk=None): - """从活动中移除产品""" - campaign = self.get_object() - product_id = request.data.get('product_id') - - if not product_id: - return api_response(code=400, message="缺少产品ID", data=None) - - try: - product = Product.objects.get(id=product_id) - campaign.link_product.remove(product) - return api_response(message="产品移除成功", data=None) - except Product.DoesNotExist: - return api_response(code=404, message="产品不存在", data=None) - except Exception as e: - return api_response(code=500, message=f"移除产品失败: {str(e)}", data=None) - - -class BrandChatSessionViewSet(viewsets.ModelViewSet): - """品牌聊天会话API视图集""" - queryset = BrandChatSession.objects.filter(is_active=True) - serializer_class = BrandChatSessionSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - serializer = self.get_serializer(queryset, many=True) - return api_response(data=serializer.data) - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="创建失败", data=serializer.errors) - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return api_response(data=serializer.data) - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return api_response(data=serializer.data) - return api_response(code=400, message="更新失败", data=serializer.errors) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - return api_response(message="删除成功", data=None) - - def perform_create(self, serializer): - # 创建聊天会话时,可以设置使用特定品牌下的所有知识库 - chat_session = serializer.save() - - # 如果没有提供dataset_id_list,则使用品牌的dataset_id_list - if not chat_session.dataset_id_list: - brand = chat_session.brand - chat_session.dataset_id_list = brand.dataset_id_list - chat_session.save(update_fields=['dataset_id_list', 'updated_at']) diff --git a/apps/discovery/README.md b/apps/discovery/README.md deleted file mode 100644 index 63d1938..0000000 --- a/apps/discovery/README.md +++ /dev/null @@ -1,329 +0,0 @@ -# Discovery API 接口文档 - -## 简介 - -Discovery API是一个用于发现和搜索创作者的接口。它提供了创作者搜索、搜索会话管理等功能。所有API响应均遵循统一格式: - -```json -{ - "code": 200, // 状态码,200表示成功 - "message": "操作成功", // 操作提示消息 - "data": { // 实际数据 - // 具体内容 - } -} -``` - -## 使用Apifox测试接口 - -1. 下载并安装Apifox: https://www.apifox.cn/ -2. 创建新项目,命名为"Creator Discovery" -3. 导入API或手动创建以下接口 - -## API 接口列表 - -### 1. 搜索创作者 - -- **URL**: `http://localhost:8000/api/discovery/creators/search/` -- **方法**: POST -- **描述**: 根据条件搜索创作者,并创建新的搜索会话 -- **请求参数**: - -```json -{ - "query": "创作者", - "category": "Health", - "ecommerce_level": "L5", - "exposure_level": "KOL-2" -} -``` - -- **响应示例**: - -```json -{ - "code": 200, - "message": "搜索创作者成功", - "data": { - "id": 4, - "creators": [ - { - "id": 37, - "name": "Mock Creator 5", - "avatar": null, - "category": "Health", - "ecommerce_level": "L5", - "exposure_level": "KOL-2", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": false, - "tiktok_url": null, - "session": 4 - } - ], - "session_number": 4, - "creator_count": 1, - "shoppable_creators": 0, - "avg_followers": 162.2, - "avg_gmv": 534.1, - "avg_video_views": 1.9, - "date_created": "2023-10-02" - } -} -``` - -### 2. 获取搜索会话列表 - -- **URL**: `http://localhost:8000/api/discovery/sessions/` -- **方法**: GET -- **描述**: 获取所有搜索会话的历史记录 -- **查询参数**: - - `page`: 页码,默认为1 - - `page_size`: 每页条数,默认为20,最大为100 -- **响应示例**: - -```json -{ - "code": 200, - "message": "获取数据成功", - "data": { - "count": 3, - "next": "http://localhost:8000/api/discovery/sessions/?page=2", - "previous": null, - "results": [ - { - "id": 1, - "session_number": 1, - "creator_count": 42, - "shoppable_creators": 24, - "avg_followers": 162.2, - "avg_gmv": 534.1, - "avg_video_views": 1.9, - "date_created": "2024-01-06" - }, - { - "id": 2, - "session_number": 2, - "creator_count": 53, - "shoppable_creators": 13, - "avg_followers": 162.2, - "avg_gmv": 534.1, - "avg_video_views": 1.9, - "date_created": "2022-01-07" - } - ] - } -} -``` - -### 3. 获取搜索会话详情 - -- **URL**: `http://localhost:8000/api/discovery/sessions/{id}/` -- **方法**: GET -- **描述**: 获取特定搜索会话的详细信息,包含该会话中的所有创作者 -- **请求参数**: 路径参数 `id` -- **响应示例**: - -```json -{ - "code": 200, - "message": "获取搜索会话详情成功", - "data": { - "id": 1, - "creators": [ - { - "id": 1, - "name": "Creator 1 in Session 1", - "avatar": null, - "category": "Phones & Electronics", - "ecommerce_level": "L2", - "exposure_level": "KOC-1", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": true, - "tiktok_url": null, - "session": 1 - }, - { - "id": 2, - "name": "Creator 9 in Session 1", - "avatar": null, - "category": "Womenswear & Underwear", - "ecommerce_level": "L3", - "exposure_level": "KOL-3", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": false, - "tiktok_url": null, - "session": 1 - } - ], - "session_number": 1, - "creator_count": 42, - "shoppable_creators": 24, - "avg_followers": 162.2, - "avg_gmv": 534.1, - "avg_video_views": 1.9, - "date_created": "2024-01-06" - } -} -``` - -### 4. 获取会话中的创作者 - -- **URL**: `http://localhost:8000/api/discovery/sessions/{id}/results/` -- **方法**: GET -- **描述**: 获取特定会话中的所有创作者 -- **请求参数**: - - 路径参数 `id` - - 查询参数 `page`、`page_size`(分页参数) -- **响应示例**: - -```json -{ - "code": 200, - "message": "获取数据成功", - "data": { - "count": 42, - "next": "http://localhost:8000/api/discovery/sessions/1/results/?page=2", - "previous": null, - "results": [ - { - "id": 1, - "name": "Creator 1 in Session 1", - "avatar": null, - "category": "Phones & Electronics", - "ecommerce_level": "L2", - "exposure_level": "KOC-1", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": true, - "tiktok_url": null, - "session": 1 - }, - { - "id": 2, - "name": "Creator 9 in Session 1", - "avatar": null, - "category": "Womenswear & Underwear", - "ecommerce_level": "L3", - "exposure_level": "KOL-3", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": false, - "tiktok_url": null, - "session": 1 - } - ] - } -} -``` - -### 5. 获取所有创作者 - -- **URL**: `http://localhost:8000/api/discovery/creators/` -- **方法**: GET -- **描述**: 获取系统中的所有创作者 -- **请求参数**: 查询参数 `page`、`page_size`(分页参数) -- **响应示例**: - -```json -{ - "code": 200, - "message": "获取数据成功", - "data": { - "count": 150, - "next": "http://localhost:8000/api/discovery/creators/?page=2", - "previous": null, - "results": [ - { - "id": 1, - "name": "Creator 1 in Session 1", - "avatar": null, - "category": "Phones & Electronics", - "ecommerce_level": "L2", - "exposure_level": "KOC-1", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": true, - "tiktok_url": null, - "session": 1 - }, - // 更多创作者... - ] - } -} -``` - -### 6. 获取创作者详情 - -- **URL**: `http://localhost:8000/api/discovery/creators/{id}/` -- **方法**: GET -- **描述**: 获取特定创作者的详细信息 -- **请求参数**: 路径参数 `id` -- **响应示例**: - -```json -{ - "code": 200, - "message": "获取创作者详情成功", - "data": { - "id": 1, - "name": "Creator 1 in Session 1", - "avatar": null, - "category": "Phones & Electronics", - "ecommerce_level": "L2", - "exposure_level": "KOC-1", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": true, - "tiktok_url": null, - "session": 1 - } -} -``` - -## 在Apifox中设置环境变量 - -创建一个名为"本地开发环境"的环境,设置以下变量: -- `baseUrl`: `http://localhost:8000/api` - -这样可以在所有请求中使用`{{baseUrl}}/discovery/...`来替代完整URL。 - -## 测试流程示例 - -1. 启动Django服务器:`python manage.py runserver` -2. 在Apifox中发送请求"获取搜索会话列表" -3. 在响应中选择一个会话ID -4. 使用该ID获取会话详情 -5. 使用"搜索创作者"接口创建新的搜索会话 -6. 验证新创建的会话是否出现在会话列表中 - -## 在Apifox中导入API - -1. 在Apifox中,点击"导入"按钮 -2. 选择"导入API" -3. 将本文档中的API信息整理成集合导入 -4. 设置每个接口的请求和响应格式 -5. 设置示例响应 - -## 注意事项 - -- 所有API响应均遵循统一的格式:`code`、`message`和`data` -- 分页接口返回格式为:`{ "code": 200, "message": "获取数据成功", "data": { "count": 总数, "next": 下一页URL, "previous": 上一页URL, "results": [] } }` -- 即使发生错误,HTTP状态码始终为200,错误信息在响应体的`code`和`message`中提供 -- 接口不需要认证,可直接访问 \ No newline at end of file diff --git a/apps/discovery/__init__.py b/apps/discovery/__init__.py deleted file mode 100644 index 95a2fa0..0000000 --- a/apps/discovery/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Discovery app for creator discovery and search. -""" diff --git a/apps/discovery/admin.py b/apps/discovery/admin.py deleted file mode 100644 index 118a202..0000000 --- a/apps/discovery/admin.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.contrib import admin -from .models import SearchSession, Creator - - -@admin.register(SearchSession) -class SearchSessionAdmin(admin.ModelAdmin): - list_display = ('session_number', 'creator_count', 'shoppable_creators', - 'avg_followers', 'avg_gmv', 'avg_video_views', 'date_created') - search_fields = ('session_number',) - list_filter = ('date_created',) - - -@admin.register(Creator) -class CreatorAdmin(admin.ModelAdmin): - list_display = ('name', 'category', 'ecommerce_level', 'exposure_level', - 'followers', 'gmv', 'avg_video_views', 'has_ecommerce') - search_fields = ('name', 'category') - list_filter = ('category', 'ecommerce_level', 'exposure_level', 'has_ecommerce') diff --git a/apps/discovery/apps.py b/apps/discovery/apps.py deleted file mode 100644 index 9da2d04..0000000 --- a/apps/discovery/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class DiscoveryConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.discovery' - verbose_name = 'Creator Discovery' diff --git a/apps/discovery/exceptions.py b/apps/discovery/exceptions.py deleted file mode 100644 index be549ff..0000000 --- a/apps/discovery/exceptions.py +++ /dev/null @@ -1,31 +0,0 @@ -from rest_framework.views import exception_handler -from rest_framework.response import Response - - -def custom_exception_handler(exc, context): - """ - 自定义异常处理 - """ - # 先调用REST framework默认的异常处理方法获得标准错误响应对象 - response = exception_handler(exc, context) - - # 如果response为None,说明REST framework无法处理该异常 - # 我们依然返回None,让Django处理该异常 - if response is None: - return None - - # 定制响应格式 - error_data = { - 'code': response.status_code, - 'message': str(exc), - 'data': None - } - - # 如果是验证错误,取出错误详情 - if hasattr(exc, 'detail'): - error_data['message'] = str(exc.detail) - if isinstance(exc.detail, dict): - error_data['data'] = exc.detail - - # 统一返回200状态码,将实际错误码放入响应体 - return Response(error_data, status=200) \ No newline at end of file diff --git a/apps/discovery/management/__init__.py b/apps/discovery/management/__init__.py deleted file mode 100644 index 43028f0..0000000 --- a/apps/discovery/management/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Discovery app management commands. -""" \ No newline at end of file diff --git a/apps/discovery/management/commands/__init__.py b/apps/discovery/management/commands/__init__.py deleted file mode 100644 index 43028f0..0000000 --- a/apps/discovery/management/commands/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Discovery app management commands. -""" \ No newline at end of file diff --git a/apps/discovery/management/commands/create_mock_discovery_data.py b/apps/discovery/management/commands/create_mock_discovery_data.py deleted file mode 100644 index 14247ba..0000000 --- a/apps/discovery/management/commands/create_mock_discovery_data.py +++ /dev/null @@ -1,73 +0,0 @@ -import random -from django.core.management.base import BaseCommand -from django.utils import timezone -from apps.discovery.models import SearchSession, Creator - - -class Command(BaseCommand): - help = '创建模拟的Discovery数据' - - def handle(self, *args, **kwargs): - # 创建3个搜索会话 - sessions = [] - for i in range(1, 4): - creator_count = random.randint(20, 100) - shoppable_creators = random.randint(3, 26) - - # 创建不同的日期 - if i == 1: - date = timezone.datetime(2024, 1, 6).date() - elif i == 2: - date = timezone.datetime(2022, 1, 7).date() - else: - date = timezone.datetime(2023, 4, 27).date() - - session = SearchSession.objects.create( - session_number=i, - creator_count=creator_count, - shoppable_creators=shoppable_creators, - avg_followers=162.2, - avg_gmv=534.1, - avg_video_views=1.9, - date_created=date - ) - sessions.append(session) - self.stdout.write(self.style.SUCCESS(f'创建会话: {session}')) - - # 创建创作者数据 - categories = [ - 'Phones & Electronics', - 'Womenswear & Underwear', - 'Sports & Outdoor', - 'Food & Beverage', - 'Health', - 'Kitchenware', - 'Furniture', - 'Shoes', - 'Home Supplies', - ] - - ecommerce_levels = ['L1', 'L2', 'L3', 'L4', 'L5', 'New tag'] - exposure_levels = ['KOC-1', 'KOC-2', 'KOL-2', 'KOL-3', 'New tag'] - - for session in sessions: - # 每个会话创建随机数量的创作者 - creator_count = random.randint(10, 20) - for i in range(creator_count): - has_ecommerce = random.choice([True, False]) - - creator = Creator.objects.create( - session=session, - name=f"Creator {i+1} in Session {session.session_number}", - category=random.choice(categories), - ecommerce_level=random.choice(ecommerce_levels), - exposure_level=random.choice(exposure_levels), - followers=162.2, - gmv=534.1, - items_sold=18.1, - avg_video_views=1.9, - has_ecommerce=has_ecommerce - ) - self.stdout.write(f'创建创作者: {creator.name}') - - self.stdout.write(self.style.SUCCESS('成功创建所有模拟数据!')) \ No newline at end of file diff --git a/apps/discovery/migrations/0001_initial.py b/apps/discovery/migrations/0001_initial.py deleted file mode 100644 index 60e63fb..0000000 --- a/apps/discovery/migrations/0001_initial.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 5.2 on 2025-05-16 02:40 - -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='SearchSession', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('session_number', models.IntegerField(default=1, verbose_name='会话编号')), - ('creator_count', models.IntegerField(default=0, verbose_name='创作者数量')), - ('shoppable_creators', models.IntegerField(default=0, verbose_name='可购物创作者数量')), - ('avg_followers', models.FloatField(default=0, verbose_name='平均粉丝数')), - ('avg_gmv', models.FloatField(default=0, verbose_name='平均GMV')), - ('avg_video_views', models.FloatField(default=0, verbose_name='平均视频观看量')), - ('date_created', models.DateField(default=django.utils.timezone.now, verbose_name='创建日期')), - ], - options={ - 'verbose_name': '搜索会话', - 'verbose_name_plural': '搜索会话', - 'ordering': ['-date_created'], - }, - ), - migrations.CreateModel( - name='Creator', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='创作者名称')), - ('avatar', models.URLField(blank=True, null=True, verbose_name='头像URL')), - ('category', models.CharField(choices=[('Phones & Electronics', 'Phones & Electronics'), ('Womenswear & Underwear', 'Womenswear & Underwear'), ('Sports & Outdoor', 'Sports & Outdoor'), ('Food & Beverage', 'Food & Beverage'), ('Health', 'Health'), ('Kitchenware', 'Kitchenware'), ('Furniture', 'Furniture'), ('Shoes', 'Shoes'), ('Home Supplies', 'Home Supplies')], max_length=50, verbose_name='类别')), - ('ecommerce_level', models.CharField(choices=[('L1', 'Level 1'), ('L2', 'Level 2'), ('L3', 'Level 3'), ('L4', 'Level 4'), ('L5', 'Level 5'), ('New tag', 'New Tag')], max_length=10, verbose_name='电商等级')), - ('exposure_level', models.CharField(choices=[('KOC-1', 'KOC-1'), ('KOC-2', 'KOC-2'), ('KOL-2', 'KOL-2'), ('KOL-3', 'KOL-3'), ('New tag', 'New Tag')], max_length=10, verbose_name='曝光等级')), - ('followers', models.FloatField(default=0, verbose_name='粉丝数')), - ('gmv', models.FloatField(default=0, verbose_name='GMV')), - ('items_sold', models.FloatField(default=0, verbose_name='销售项目数')), - ('avg_video_views', models.FloatField(default=0, verbose_name='平均视频观看量')), - ('has_ecommerce', models.BooleanField(default=False, verbose_name='是否有电商')), - ('tiktok_url', models.URLField(blank=True, null=True, verbose_name='抖音链接')), - ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='creators', to='discovery.searchsession', verbose_name='所属会话')), - ], - options={ - 'verbose_name': '创作者', - 'verbose_name_plural': '创作者', - 'ordering': ['name'], - }, - ), - ] diff --git a/apps/discovery/migrations/0002_alter_searchsession_date_created.py b/apps/discovery/migrations/0002_alter_searchsession_date_created.py deleted file mode 100644 index 5a7b7dd..0000000 --- a/apps/discovery/migrations/0002_alter_searchsession_date_created.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2 on 2025-05-16 03:12 - -import apps.discovery.models -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('discovery', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='searchsession', - name='date_created', - field=models.DateField(default=apps.discovery.models.get_current_date, verbose_name='创建日期'), - ), - ] diff --git a/apps/discovery/migrations/__init__.py b/apps/discovery/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/discovery/models.py b/apps/discovery/models.py deleted file mode 100644 index a9f6459..0000000 --- a/apps/discovery/models.py +++ /dev/null @@ -1,79 +0,0 @@ -from django.db import models -from django.utils import timezone - - -def get_current_date(): - """返回当前日期(非日期时间)""" - return timezone.now().date() - - -class SearchSession(models.Model): - """搜索历史记录""" - session_number = models.IntegerField(default=1, verbose_name="会话编号") - creator_count = models.IntegerField(default=0, verbose_name="创作者数量") - shoppable_creators = models.IntegerField(default=0, verbose_name="可购物创作者数量") - avg_followers = models.FloatField(default=0, verbose_name="平均粉丝数") - avg_gmv = models.FloatField(default=0, verbose_name="平均GMV") - avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量") - date_created = models.DateField(default=get_current_date, verbose_name="创建日期") - - class Meta: - verbose_name = "搜索会话" - verbose_name_plural = "搜索会话" - ordering = ['-date_created'] - - def __str__(self): - return f"会话 {self.session_number} - {self.date_created}" - - -class Creator(models.Model): - """创作者信息""" - ECOMMERCE_LEVELS = [ - ('L1', 'Level 1'), - ('L2', 'Level 2'), - ('L3', 'Level 3'), - ('L4', 'Level 4'), - ('L5', 'Level 5'), - ('New tag', 'New Tag'), - ] - - EXPOSURE_LEVELS = [ - ('KOC-1', 'KOC-1'), - ('KOC-2', 'KOC-2'), - ('KOL-2', 'KOL-2'), - ('KOL-3', 'KOL-3'), - ('New tag', 'New Tag'), - ] - - CATEGORIES = [ - ('Phones & Electronics', 'Phones & Electronics'), - ('Womenswear & Underwear', 'Womenswear & Underwear'), - ('Sports & Outdoor', 'Sports & Outdoor'), - ('Food & Beverage', 'Food & Beverage'), - ('Health', 'Health'), - ('Kitchenware', 'Kitchenware'), - ('Furniture', 'Furniture'), - ('Shoes', 'Shoes'), - ('Home Supplies', 'Home Supplies'), - ] - - session = models.ForeignKey(SearchSession, on_delete=models.CASCADE, related_name='creators', verbose_name="所属会话") - name = models.CharField(max_length=100, verbose_name="创作者名称") - avatar = models.URLField(blank=True, null=True, verbose_name="头像URL") - category = models.CharField(max_length=50, choices=CATEGORIES, verbose_name="类别") - ecommerce_level = models.CharField(max_length=10, choices=ECOMMERCE_LEVELS, verbose_name="电商等级") - exposure_level = models.CharField(max_length=10, choices=EXPOSURE_LEVELS, verbose_name="曝光等级") - followers = models.FloatField(default=0, verbose_name="粉丝数") - gmv = models.FloatField(default=0, verbose_name="GMV") - items_sold = models.FloatField(default=0, verbose_name="销售项目数") - avg_video_views = models.FloatField(default=0, verbose_name="平均视频观看量") - has_ecommerce = models.BooleanField(default=False, verbose_name="是否有电商") - tiktok_url = models.URLField(blank=True, null=True, verbose_name="抖音链接") - - class Meta: - verbose_name = "创作者" - verbose_name_plural = "创作者" - ordering = ['name'] - - def __str__(self): - return self.name diff --git a/apps/discovery/pagination.py b/apps/discovery/pagination.py deleted file mode 100644 index 508301d..0000000 --- a/apps/discovery/pagination.py +++ /dev/null @@ -1,22 +0,0 @@ -from rest_framework.pagination import PageNumberPagination -from rest_framework.response import Response - - -class StandardResultsSetPagination(PageNumberPagination): - """标准分页器""" - page_size = 20 - page_size_query_param = 'page_size' - max_page_size = 100 - - def get_paginated_response(self, data): - """自定义分页响应格式""" - return Response({ - "code": 200, - "message": "获取数据成功", - "data": { - "count": self.page.paginator.count, - "next": self.get_next_link(), - "previous": self.get_previous_link(), - "results": data - } - }) \ No newline at end of file diff --git a/apps/discovery/serializers.py b/apps/discovery/serializers.py deleted file mode 100644 index a178314..0000000 --- a/apps/discovery/serializers.py +++ /dev/null @@ -1,35 +0,0 @@ -from rest_framework import serializers -from .models import SearchSession, Creator - - -class CreatorSerializer(serializers.ModelSerializer): - """创作者序列化器""" - - class Meta: - model = Creator - fields = '__all__' - - -class CreatorDetailSerializer(serializers.ModelSerializer): - """创作者详细信息序列化器""" - - class Meta: - model = Creator - fields = '__all__' - - -class SearchSessionSerializer(serializers.ModelSerializer): - """搜索会话序列化器""" - - class Meta: - model = SearchSession - fields = '__all__' - - -class SearchSessionDetailSerializer(serializers.ModelSerializer): - """搜索会话详细信息序列化器,包含创作者数据""" - creators = CreatorSerializer(many=True, read_only=True) - - class Meta: - model = SearchSession - fields = '__all__' \ No newline at end of file diff --git a/apps/discovery/tests.py b/apps/discovery/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/apps/discovery/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/discovery/urls.py b/apps/discovery/urls.py deleted file mode 100644 index a5ab756..0000000 --- a/apps/discovery/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path, include -from rest_framework.routers import DefaultRouter - -from .views import SearchSessionViewSet, CreatorDiscoveryViewSet - -router = DefaultRouter() -router.register(r'sessions', SearchSessionViewSet) -router.register(r'creators', CreatorDiscoveryViewSet) - -urlpatterns = [ - path('', include(router.urls)), -] \ No newline at end of file diff --git a/apps/discovery/views.py b/apps/discovery/views.py deleted file mode 100644 index 084847d..0000000 --- a/apps/discovery/views.py +++ /dev/null @@ -1,276 +0,0 @@ -from django.shortcuts import render -from django.db.models import Q -from rest_framework import viewsets, status -from rest_framework.decorators import action -from rest_framework.response import Response -from rest_framework.permissions import AllowAny -from django.utils import timezone - -from .models import SearchSession, Creator -from .serializers import ( - SearchSessionSerializer, - SearchSessionDetailSerializer, - CreatorSerializer, - CreatorDetailSerializer -) -from .pagination import StandardResultsSetPagination - - -class ApiResponse: - """API统一响应格式""" - @staticmethod - def success(data=None, message="操作成功"): - return Response({ - "code": 200, - "message": message, - "data": data - }) - - @staticmethod - def error(message="操作失败", code=400, data=None): - return Response({ - "code": code, - "message": message, - "data": data - }, status=status.HTTP_200_OK) # 始终返回200状态码,错误信息在内容中提供 - - -class SearchSessionViewSet(viewsets.ModelViewSet): - """搜索会话视图集""" - queryset = SearchSession.objects.all() - serializer_class = SearchSessionSerializer - permission_classes = [AllowAny] - pagination_class = StandardResultsSetPagination - - def get_serializer_class(self): - if self.action == 'retrieve': - return SearchSessionDetailSerializer - return SearchSessionSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success(serializer.data, "获取搜索会话列表成功") - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return ApiResponse.success(serializer.data, "获取搜索会话详情成功") - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - headers = self.get_success_headers(serializer.data) - return ApiResponse.success(serializer.data, "创建搜索会话成功") - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) - return ApiResponse.success(serializer.data, "更新搜索会话成功") - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - return ApiResponse.success(None, "删除搜索会话成功") - - @action(detail=True, methods=['get']) - def results(self, request, pk=None): - """获取指定会话的搜索结果""" - session = self.get_object() - creators = session.creators.all() - page = self.paginate_queryset(creators) - if page is not None: - serializer = CreatorSerializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = CreatorSerializer(creators, many=True) - return ApiResponse.success(serializer.data, "获取会话创作者列表成功") - - -class CreatorDiscoveryViewSet(viewsets.ReadOnlyModelViewSet): - """创作者发现视图集""" - queryset = Creator.objects.all() - serializer_class = CreatorSerializer - permission_classes = [AllowAny] - pagination_class = StandardResultsSetPagination - - def get_serializer_class(self): - if self.action == 'retrieve': - return CreatorDetailSerializer - return CreatorSerializer - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success(serializer.data, "获取创作者列表成功") - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return ApiResponse.success(serializer.data, "获取创作者详情成功") - - @action(detail=False, methods=['post']) - def search(self, request): - """搜索创作者""" - query = request.data.get('query', '') - category = request.data.get('category', None) - ecommerce_level = request.data.get('ecommerce_level', None) - exposure_level = request.data.get('exposure_level', None) - - # 创建模拟搜索会话 - session = self._create_mock_search_session() - - # 生成模拟搜索结果 - creators = self._generate_mock_creators(session, query, category, ecommerce_level, exposure_level) - - # 返回会话详情 - serializer = SearchSessionDetailSerializer(session) - return ApiResponse.success(serializer.data, "搜索创作者成功") - - def _create_mock_search_session(self): - """创建模拟搜索会话""" - # 获取当前最大会话编号并加1 - 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 - - # 创建新会话 - session = SearchSession.objects.create( - session_number=session_number, - creator_count=100, - shoppable_creators=26, - avg_followers=162.2, - avg_gmv=534.1, - avg_video_views=1.9, - date_created=timezone.now().date() # 将datetime转换为date类型 - ) - return session - - def _generate_mock_creators(self, session, query, category=None, ecommerce_level=None, exposure_level=None): - """生成模拟创作者数据""" - # 模拟数据 - 这里可以根据实际需求调整 - mock_creators = [ - { - "name": "Mock Creator 1", - "category": "Phones & Electronics", - "ecommerce_level": "L2", - "exposure_level": "KOC-1", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": True - }, - { - "name": "Mock Creator 2", - "category": "Womenswear & Underwear", - "ecommerce_level": "L3", - "exposure_level": "KOL-3", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": False - }, - { - "name": "Mock Creator 3", - "category": "Sports & Outdoor", - "ecommerce_level": "L4", - "exposure_level": "KOC-2", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": True - }, - { - "name": "Mock Creator 4", - "category": "Food & Beverage", - "ecommerce_level": "L1", - "exposure_level": "KOC-2", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": True - }, - { - "name": "Mock Creator 5", - "category": "Health", - "ecommerce_level": "L5", - "exposure_level": "KOL-2", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": False - }, - { - "name": "Mock Creator 6", - "category": "Kitchenware", - "ecommerce_level": "New tag", - "exposure_level": "New tag", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": True - }, - { - "name": "Mock Creator 7", - "category": "Furniture", - "ecommerce_level": "New tag", - "exposure_level": "New tag", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": False - }, - { - "name": "Mock Creator 8", - "category": "Shoes", - "ecommerce_level": "New tag", - "exposure_level": "New tag", - "followers": 162.2, - "gmv": 534.1, - "items_sold": 18.1, - "avg_video_views": 1.9, - "has_ecommerce": True - }, - ] - - # 根据查询条件过滤模拟数据 - filtered_creators = mock_creators - if category: - filtered_creators = [c for c in filtered_creators if c['category'] == category] - if ecommerce_level: - filtered_creators = [c for c in filtered_creators if c['ecommerce_level'] == ecommerce_level] - if exposure_level: - filtered_creators = [c for c in filtered_creators if c['exposure_level'] == exposure_level] - - # 创建创作者记录 - for creator_data in filtered_creators: - Creator.objects.create( - session=session, - **creator_data - ) - - # 更新会话统计信息 - session.creator_count = len(filtered_creators) - session.shoppable_creators = len([c for c in filtered_creators if c['has_ecommerce']]) - session.save() - - return filtered_creators diff --git a/apps/template/admin.py b/apps/template/admin.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/template/apps.py b/apps/template/apps.py deleted file mode 100644 index 82a8d85..0000000 --- a/apps/template/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class TemplateConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.template' - verbose_name = '模板管理' diff --git a/apps/template/exceptions.py b/apps/template/exceptions.py deleted file mode 100644 index ebae83d..0000000 --- a/apps/template/exceptions.py +++ /dev/null @@ -1,59 +0,0 @@ -from rest_framework.views import exception_handler -from rest_framework.exceptions import APIException -from rest_framework import status -from django.http import Http404 -from django.core.exceptions import ValidationError -from django.db.utils import IntegrityError -from rest_framework.response import Response - -def custom_exception_handler(exc, context): - """ - 自定义异常处理器,将所有异常转换为标准响应格式 - - Args: - exc: 异常对象 - context: 异常上下文 - - Returns: - 标准格式的Response对象 - """ - response = exception_handler(exc, context) - - if response is not None: - # 已经被DRF处理的异常,转换为标准格式 - return Response({ - 'code': response.status_code, - 'message': str(exc), - 'data': response.data if hasattr(response, 'data') else None - }, status=response.status_code) - - # 如果是Django的404错误 - if isinstance(exc, Http404): - return Response({ - 'code': status.HTTP_404_NOT_FOUND, - 'message': '请求的资源不存在', - 'data': None - }, status=status.HTTP_404_NOT_FOUND) - - # 如果是验证错误 - if isinstance(exc, ValidationError): - return Response({ - 'code': status.HTTP_400_BAD_REQUEST, - 'message': '数据验证失败', - 'data': str(exc) if str(exc) else '提供的数据无效' - }, status=status.HTTP_400_BAD_REQUEST) - - # 如果是数据库完整性错误(如唯一约束) - if isinstance(exc, IntegrityError): - return Response({ - 'code': status.HTTP_400_BAD_REQUEST, - 'message': '数据库完整性错误', - 'data': str(exc) - }, status=status.HTTP_400_BAD_REQUEST) - - # 其他未处理的异常 - return Response({ - 'code': status.HTTP_500_INTERNAL_SERVER_ERROR, - 'message': '服务器内部错误', - 'data': str(exc) if str(exc) else None - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file diff --git a/apps/template/filters.py b/apps/template/filters.py deleted file mode 100644 index e961cb7..0000000 --- a/apps/template/filters.py +++ /dev/null @@ -1,26 +0,0 @@ -import django_filters -from .models import Template - -class TemplateFilter(django_filters.FilterSet): - """模板过滤器""" - title = django_filters.CharFilter(field_name='title', lookup_expr='icontains') - content = django_filters.CharFilter(field_name='content', lookup_expr='icontains') - mission = django_filters.CharFilter(field_name='mission') - platform = django_filters.CharFilter(field_name='platform') - collaboration_type = django_filters.CharFilter(field_name='collaboration_type') - service = django_filters.CharFilter(field_name='service') - category = django_filters.NumberFilter(field_name='category__id') - category_name = django_filters.CharFilter(field_name='category__name', lookup_expr='icontains') - created_by = django_filters.NumberFilter(field_name='created_by__id') - is_public = django_filters.BooleanFilter(field_name='is_public') - created_after = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte') - created_before = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte') - - class Meta: - model = Template - fields = [ - 'title', 'content', 'mission', 'platform', - 'collaboration_type', 'service', 'category', - 'category_name', 'created_by', 'is_public', - 'created_after', 'created_before' - ] \ No newline at end of file diff --git a/apps/template/migrations/0001_initial.py b/apps/template/migrations/0001_initial.py deleted file mode 100644 index 24fec55..0000000 --- a/apps/template/migrations/0001_initial.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 5.2 on 2025-05-19 04:13 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='TemplateCategory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='分类名称')), - ('description', models.TextField(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='更新时间')), - ], - options={ - 'verbose_name': '模板分类', - 'verbose_name_plural': '模板分类', - }, - ), - migrations.CreateModel( - name='Template', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=200, verbose_name='模板标题')), - ('content', models.TextField(verbose_name='模板内容')), - ('preview', models.TextField(blank=True, null=True, verbose_name='内容预览')), - ('mission', models.CharField(choices=[('initial_contact', '初步联系'), ('follow_up', '跟进'), ('negotiation', '谈判'), ('closing', '成交'), ('other', '其他')], default='initial_contact', max_length=50, verbose_name='任务类型')), - ('platform', models.CharField(choices=[('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('youtube', 'YouTube'), ('facebook', 'Facebook'), ('twitter', 'Twitter'), ('other', '其他')], default='tiktok', max_length=50, verbose_name='平台')), - ('collaboration_type', models.CharField(choices=[('paid_promotion', '付费推广'), ('affiliate', '联盟营销'), ('sponsored_content', '赞助内容'), ('brand_ambassador', '品牌大使'), ('other', '其他')], default='paid_promotion', max_length=50, verbose_name='合作模式')), - ('service', models.CharField(choices=[('voice', '声优 - 交谈'), ('text', '文本'), ('video', '视频'), ('image', '图片'), ('other', '其他')], default='text', max_length=50, verbose_name='服务类型')), - ('is_public', models.BooleanField(default=True, verbose_name='是否公开')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_templates', to=settings.AUTH_USER_MODEL, verbose_name='创建者')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='template.templatecategory', verbose_name='模板分类')), - ], - options={ - 'verbose_name': '模板', - 'verbose_name_plural': '模板', - }, - ), - ] diff --git a/apps/template/migrations/0002_remove_template_created_by.py b/apps/template/migrations/0002_remove_template_created_by.py deleted file mode 100644 index a3dafce..0000000 --- a/apps/template/migrations/0002_remove_template_created_by.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2 on 2025-05-20 09:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('template', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='template', - name='created_by', - ), - ] diff --git a/apps/template/migrations/__init__.py b/apps/template/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/template/models.py b/apps/template/models.py deleted file mode 100644 index cb60472..0000000 --- a/apps/template/models.py +++ /dev/null @@ -1,77 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -class TemplateCategory(models.Model): - """模板分类模型""" - name = models.CharField(_('分类名称'), max_length=100) - description = models.TextField(_('分类描述'), blank=True, null=True) - created_at = models.DateTimeField(_('创建时间'), auto_now_add=True) - updated_at = models.DateTimeField(_('更新时间'), auto_now=True) - - class Meta: - verbose_name = _('模板分类') - verbose_name_plural = _('模板分类') - - def __str__(self): - return self.name - -class Template(models.Model): - """模板模型""" - MISSION_CHOICES = [ - ('initial_contact', '初步联系'), - ('follow_up', '跟进'), - ('negotiation', '谈判'), - ('closing', '成交'), - ('other', '其他'), - ] - - PLATFORM_CHOICES = [ - ('tiktok', 'TikTok'), - ('instagram', 'Instagram'), - ('youtube', 'YouTube'), - ('facebook', 'Facebook'), - ('twitter', 'Twitter'), - ('other', '其他'), - ] - - COLLABORATION_CHOICES = [ - ('paid_promotion', '付费推广'), - ('affiliate', '联盟营销'), - ('sponsored_content', '赞助内容'), - ('brand_ambassador', '品牌大使'), - ('other', '其他'), - ] - - SERVICE_CHOICES = [ - ('voice', '声优 - 交谈'), - ('text', '文本'), - ('video', '视频'), - ('image', '图片'), - ('other', '其他'), - ] - - title = models.CharField(_('模板标题'), max_length=200) - content = models.TextField(_('模板内容')) - preview = models.TextField(_('内容预览'), blank=True, null=True) - category = models.ForeignKey(TemplateCategory, on_delete=models.CASCADE, related_name='templates', verbose_name=_('模板分类')) - mission = models.CharField(_('任务类型'), max_length=50, choices=MISSION_CHOICES, default='initial_contact') - platform = models.CharField(_('平台'), max_length=50, choices=PLATFORM_CHOICES, default='tiktok') - collaboration_type = models.CharField(_('合作模式'), max_length=50, choices=COLLABORATION_CHOICES, default='paid_promotion') - service = models.CharField(_('服务类型'), max_length=50, choices=SERVICE_CHOICES, default='text') - is_public = models.BooleanField(_('是否公开'), default=True) - created_at = models.DateTimeField(_('创建时间'), auto_now_add=True) - updated_at = models.DateTimeField(_('更新时间'), auto_now=True) - - class Meta: - verbose_name = _('模板') - verbose_name_plural = _('模板') - - def __str__(self): - return self.title - - def save(self, *args, **kwargs): - # 自动生成内容预览 - if self.content and not self.preview: - # 截取前100个字符作为预览 - self.preview = self.content[:100] + ('...' if len(self.content) > 100 else '') - super().save(*args, **kwargs) diff --git a/apps/template/pagination.py b/apps/template/pagination.py deleted file mode 100644 index 28cc9a1..0000000 --- a/apps/template/pagination.py +++ /dev/null @@ -1,37 +0,0 @@ -from rest_framework.pagination import PageNumberPagination -from rest_framework.response import Response - -class StandardResultsSetPagination(PageNumberPagination): - """标准分页器,支持标准响应格式""" - page_size = 10 - page_size_query_param = 'page_size' - max_page_size = 100 - - def get_paginated_response(self, data): - """ - 返回标准格式的分页响应 - - Args: - data: 已经封装为标准格式的响应数据 - """ - # 如果data已经是标准格式{code, message, data},则用data['data']取出实际数据 - actual_data = data.get('data') if isinstance(data, dict) and 'data' in data else data - - # 准备分页元数据 - pagination_data = { - 'count': self.page.paginator.count, - 'next': self.get_next_link(), - 'previous': self.get_previous_link(), - 'results': actual_data, - 'total_pages': self.page.paginator.num_pages, - 'current_page': self.page.number, - } - - # 如果data是标准格式,则保持原有message和code,否则使用默认值 - response_data = { - 'code': data.get('code', 200) if isinstance(data, dict) and 'code' in data else 200, - 'message': data.get('message', '获取数据成功') if isinstance(data, dict) and 'message' in data else '获取数据成功', - 'data': pagination_data - } - - return Response(response_data) \ No newline at end of file diff --git a/apps/template/serializers.py b/apps/template/serializers.py deleted file mode 100644 index 8713196..0000000 --- a/apps/template/serializers.py +++ /dev/null @@ -1,104 +0,0 @@ -from rest_framework import serializers -from .models import Template, TemplateCategory - -class TemplateCategorySerializer(serializers.ModelSerializer): - """模板分类序列化器""" - class Meta: - model = TemplateCategory - fields = ['id', 'name', 'description', 'created_at', 'updated_at'] - read_only_fields = ['created_at', 'updated_at'] - -class TemplateListSerializer(serializers.ModelSerializer): - """模板列表序列化器(简化版)""" - category_name = serializers.CharField(source='category.name', read_only=True) - mission_display = serializers.CharField(source='get_mission_display', read_only=True) - platform_display = serializers.CharField(source='get_platform_display', read_only=True) - collaboration_type_display = serializers.CharField(source='get_collaboration_type_display', read_only=True) - service_display = serializers.CharField(source='get_service_display', read_only=True) - - class Meta: - model = Template - fields = [ - 'id', 'title', 'preview', 'category_name', - 'mission', 'mission_display', - 'platform', 'platform_display', - 'service', 'service_display', - 'collaboration_type', 'collaboration_type_display', - 'is_public', 'created_at', 'updated_at' - ] - read_only_fields = ['created_at', 'updated_at', 'preview'] - -class TemplateDetailSerializer(serializers.ModelSerializer): - """模板详情序列化器""" - category = TemplateCategorySerializer(read_only=True) - category_id = serializers.IntegerField(write_only=True, required=False) - mission_display = serializers.CharField(source='get_mission_display', read_only=True) - platform_display = serializers.CharField(source='get_platform_display', read_only=True) - collaboration_type_display = serializers.CharField(source='get_collaboration_type_display', read_only=True) - service_display = serializers.CharField(source='get_service_display', read_only=True) - - class Meta: - model = Template - fields = [ - 'id', 'title', 'content', 'preview', - 'category', 'category_id', - 'mission', 'mission_display', - 'platform', 'platform_display', - 'service', 'service_display', - 'collaboration_type', 'collaboration_type_display', - 'is_public', 'created_at', 'updated_at' - ] - read_only_fields = ['created_at', 'updated_at', 'preview'] - - def create(self, validated_data): - """创建模板""" - # 处理category_id字段 - category_id = validated_data.pop('category_id', None) - if category_id: - try: - category = TemplateCategory.objects.get(id=category_id) - validated_data['category'] = category - except TemplateCategory.DoesNotExist: - # 如果分类不存在,创建一个默认分类 - category = TemplateCategory.objects.create(name="默认分类") - validated_data['category'] = category - - return super().create(validated_data) - -class TemplateCreateUpdateSerializer(serializers.ModelSerializer): - """模板创建和更新序列化器""" - class Meta: - model = Template - fields = [ - 'id', 'title', 'content', - 'category', 'mission', 'platform', - 'service', 'collaboration_type', - 'is_public' - ] - read_only_fields = ['preview'] - - def validate(self, data): - """验证数据,处理测试期间可能缺失的字段""" - # 处理category字段,确保有有效的分类 - if 'category' not in data: - # 获取或创建默认分类 - category, created = TemplateCategory.objects.get_or_create(name="默认分类") - data['category'] = category - - # 确保其他必填字段有默认值 - if 'mission' not in data: - data['mission'] = 'initial_contact' - - if 'platform' not in data: - data['platform'] = 'tiktok' - - if 'service' not in data: - data['service'] = 'text' - - if 'collaboration_type' not in data: - data['collaboration_type'] = 'paid_promotion' - - if 'is_public' not in data: - data['is_public'] = True - - return data \ No newline at end of file diff --git a/apps/template/tests.py b/apps/template/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/apps/template/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/template/urls.py b/apps/template/urls.py deleted file mode 100644 index e23de04..0000000 --- a/apps/template/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path, include -from rest_framework.routers import DefaultRouter -from .views import TemplateViewSet, TemplateCategoryViewSet - -router = DefaultRouter() -router.register(r'categories', TemplateCategoryViewSet) -router.register(r'', TemplateViewSet) - -urlpatterns = [ - path('', include(router.urls)), -] \ No newline at end of file diff --git a/apps/template/utils.py b/apps/template/utils.py deleted file mode 100644 index f048c06..0000000 --- a/apps/template/utils.py +++ /dev/null @@ -1,36 +0,0 @@ -from rest_framework.response import Response - -class ApiResponse: - """API标准响应格式工具类""" - - @staticmethod - def success(data=None, message="操作成功", code=200): - """ - 返回成功响应 - - Args: - data: 响应数据 - message: 成功消息 - code: 状态码 - """ - return Response({ - "code": code, - "message": message, - "data": data - }) - - @staticmethod - def error(message="操作失败", code=400, data=None): - """ - 返回错误响应 - - Args: - message: 错误消息 - code: 错误状态码 - data: 额外数据 - """ - return Response({ - "code": code, - "message": message, - "data": data - }, status=code) \ No newline at end of file diff --git a/apps/template/views.py b/apps/template/views.py deleted file mode 100644 index b9835f0..0000000 --- a/apps/template/views.py +++ /dev/null @@ -1,297 +0,0 @@ -from django.shortcuts import render -from rest_framework import viewsets, permissions, status, filters -from rest_framework.response import Response -from rest_framework.decorators import action -from django_filters.rest_framework import DjangoFilterBackend -from .models import Template, TemplateCategory -from .serializers import ( - TemplateListSerializer, - TemplateDetailSerializer, - TemplateCreateUpdateSerializer, - TemplateCategorySerializer -) -from .filters import TemplateFilter -from .utils import ApiResponse -from .pagination import StandardResultsSetPagination - -# Create your views here. - -class TemplateCategoryViewSet(viewsets.ModelViewSet): - """ - 模板分类视图集 - - 提供模板分类的增删改查功能 - """ - queryset = TemplateCategory.objects.all() - serializer_class = TemplateCategorySerializer - permission_classes = [permissions.AllowAny] # 允许所有人访问 - pagination_class = StandardResultsSetPagination - - def list(self, request, *args, **kwargs): - """获取所有模板分类""" - queryset = self.filter_queryset(self.get_queryset()) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="获取模板分类列表成功" - ) - - def retrieve(self, request, *args, **kwargs): - """获取单个模板分类详情""" - instance = self.get_object() - serializer = self.get_serializer(instance) - return ApiResponse.success( - data=serializer.data, - message="获取模板分类详情成功" - ) - - def create(self, request, *args, **kwargs): - """创建模板分类""" - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return ApiResponse.success( - data=serializer.data, - message="模板分类创建成功", - code=status.HTTP_201_CREATED - ) - return ApiResponse.error( - message="模板分类创建失败", - data=serializer.errors, - code=status.HTTP_400_BAD_REQUEST - ) - - def update(self, request, *args, **kwargs): - """更新模板分类""" - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return ApiResponse.success( - data=serializer.data, - message="模板分类更新成功" - ) - return ApiResponse.error( - message="模板分类更新失败", - data=serializer.errors, - code=status.HTTP_400_BAD_REQUEST - ) - - def destroy(self, request, *args, **kwargs): - """删除模板分类""" - instance = self.get_object() - self.perform_destroy(instance) - return ApiResponse.success( - data=None, - message="模板分类删除成功" - ) - -class TemplateViewSet(viewsets.ModelViewSet): - """ - 模板视图集 - - 提供模板的增删改查功能 - """ - queryset = Template.objects.all() - permission_classes = [permissions.AllowAny] # 允许所有人访问 - filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] - filterset_class = TemplateFilter - search_fields = ['title', 'content'] - ordering_fields = ['created_at', 'updated_at', 'title'] - ordering = ['-created_at'] - pagination_class = StandardResultsSetPagination - - def get_queryset(self): - """ - 自定义查询集,返回所有模板 - """ - return Template.objects.all() - - def get_serializer_class(self): - """根据不同的操作返回不同的序列化器""" - if self.action == 'list': - return TemplateListSerializer - elif self.action in ['create', 'update', 'partial_update']: - return TemplateCreateUpdateSerializer - return TemplateDetailSerializer - - def list(self, request, *args, **kwargs): - """获取所有模板""" - queryset = self.filter_queryset(self.get_queryset()) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="获取模板列表成功" - ) - - def retrieve(self, request, *args, **kwargs): - """获取单个模板详情""" - instance = self.get_object() - serializer = self.get_serializer(instance) - return ApiResponse.success( - data=serializer.data, - message="获取模板详情成功" - ) - - def create(self, request, *args, **kwargs): - """创建模板""" - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - self.perform_create(serializer) - return ApiResponse.success( - data=serializer.data, - message="模板创建成功", - code=status.HTTP_201_CREATED - ) - return ApiResponse.error( - message="模板创建失败", - data=serializer.errors, - code=status.HTTP_400_BAD_REQUEST - ) - - def update(self, request, *args, **kwargs): - """更新模板""" - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - if serializer.is_valid(): - self.perform_update(serializer) - return ApiResponse.success( - data=serializer.data, - message="模板更新成功" - ) - return ApiResponse.error( - message="模板更新失败", - data=serializer.errors, - code=status.HTTP_400_BAD_REQUEST - ) - - def destroy(self, request, *args, **kwargs): - """删除模板""" - instance = self.get_object() - self.perform_destroy(instance) - return ApiResponse.success( - data=None, - message="模板删除成功" - ) - - @action(detail=False, methods=['get']) - def mine(self, request): - """获取所有模板""" - templates = Template.objects.all() - page = self.paginate_queryset(templates) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(templates, many=True) - return ApiResponse.success( - data=serializer.data, - message="获取模板成功" - ) - - @action(detail=False, methods=['get']) - def public(self, request): - """获取所有公开的模板""" - templates = Template.objects.filter(is_public=True) - page = self.paginate_queryset(templates) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(templates, many=True) - return ApiResponse.success( - data=serializer.data, - message="获取公开模板成功" - ) - - @action(detail=False, methods=['get']) - def by_mission(self, request): - """按任务类型获取模板""" - mission = request.query_params.get('mission', None) - if not mission: - return ApiResponse.error( - message="需要提供mission参数", - code=status.HTTP_400_BAD_REQUEST - ) - - queryset = self.get_queryset().filter(mission=mission) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="按任务类型获取模板成功" - ) - - @action(detail=False, methods=['get']) - def by_platform(self, request): - """按平台获取模板""" - platform = request.query_params.get('platform', None) - if not platform: - return ApiResponse.error( - message="需要提供platform参数", - code=status.HTTP_400_BAD_REQUEST - ) - - queryset = self.get_queryset().filter(platform=platform) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="按平台获取模板成功" - ) - - @action(detail=False, methods=['get']) - def by_collaboration(self, request): - """按合作模式获取模板""" - collaboration_type = request.query_params.get('collaboration_type', None) - if not collaboration_type: - return ApiResponse.error( - message="需要提供collaboration_type参数", - code=status.HTTP_400_BAD_REQUEST - ) - - queryset = self.get_queryset().filter(collaboration_type=collaboration_type) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="按合作模式获取模板成功" - ) - - @action(detail=False, methods=['get']) - def by_service(self, request): - """按服务类型获取模板""" - service = request.query_params.get('service', None) - if not service: - return ApiResponse.error( - message="需要提供service参数", - code=status.HTTP_400_BAD_REQUEST - ) - - queryset = self.get_queryset().filter(service=service) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return ApiResponse.success( - data=serializer.data, - message="按服务类型获取模板成功" - ) diff --git a/daren_project/settings.py b/daren_project/settings.py index 085a806..fa4327f 100644 --- a/daren_project/settings.py +++ b/daren_project/settings.py @@ -52,10 +52,7 @@ INSTALLED_APPS = [ 'apps.gmail', 'apps.feishu', 'apps.common', - 'apps.brands', 'apps.operation', - 'apps.discovery', # 新添加的Discovery应用 - 'apps.template', # 新添加的Template应用 'django_celery_beat', # Celery定时任务 'django_celery_results', # Celery结果存储 ] diff --git a/daren_project/urls.py b/daren_project/urls.py index cc95320..413319d 100644 --- a/daren_project/urls.py +++ b/daren_project/urls.py @@ -26,8 +26,5 @@ urlpatterns = [ path('api/notification/', include('apps.notification.urls')), path('api/gmail/', include('apps.gmail.urls')), path('api/feishu/', include('apps.feishu.urls')), - path('api/', include('apps.brands.urls')), path('api/operation/', include('apps.operation.urls')), - path('api/discovery/', include('apps.discovery.urls')), - path('api/templates/', include('apps.template.urls')), ] \ No newline at end of file