修改权限
This commit is contained in:
parent
4a2e7f1222
commit
b68062051a
211
AVATAR_SETUP_README.md
Normal file
211
AVATAR_SETUP_README.md
Normal file
@ -0,0 +1,211 @@
|
||||
# CreatorProfile头像字段本地图片支持方案
|
||||
|
||||
## 问题解答
|
||||
|
||||
**问题**: `avatar_url` 字段可以使用本地的图片显示吗?
|
||||
|
||||
**答案**: 可以!我已经为您实现了一个完整的解决方案,支持本地图片上传和外部URL两种方式。
|
||||
|
||||
## 解决方案概述
|
||||
|
||||
### 1. 模型更改
|
||||
|
||||
在 `CreatorProfile` 模型中添加了新的 `avatar` 字段:
|
||||
|
||||
```python
|
||||
# apps/daren_detail/models.py
|
||||
class CreatorProfile(models.Model):
|
||||
# 原有字段
|
||||
name = models.CharField(max_length=255, verbose_name="达人名称")
|
||||
|
||||
# 新增:支持本地图片上传
|
||||
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, verbose_name="头像图片")
|
||||
|
||||
# 保留:外部URL支持(向后兼容)
|
||||
avatar_url = models.TextField(blank=True, null=True, verbose_name="头像URL")
|
||||
|
||||
def get_avatar_url(self):
|
||||
"""获取头像URL,优先返回本地图片,其次返回外部URL"""
|
||||
if self.avatar:
|
||||
return self.avatar.url
|
||||
elif self.avatar_url:
|
||||
return self.avatar_url
|
||||
return None
|
||||
```
|
||||
|
||||
### 2. Django设置配置
|
||||
|
||||
添加了媒体文件支持:
|
||||
|
||||
```python
|
||||
# daren/settings.py
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
```
|
||||
|
||||
```python
|
||||
# daren/urls.py
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
# 在开发环境中提供媒体文件服务
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
```
|
||||
|
||||
### 3. 序列化器支持
|
||||
|
||||
创建了专门的序列化器来处理头像字段:
|
||||
|
||||
```python
|
||||
# apps/daren_detail/serializers.py
|
||||
class CreatorProfileSerializer(serializers.ModelSerializer):
|
||||
avatar_display_url = serializers.SerializerMethodField()
|
||||
|
||||
def get_avatar_display_url(self, obj):
|
||||
"""智能返回头像URL,优先本地图片"""
|
||||
request = self.context.get('request')
|
||||
avatar_url = obj.get_avatar_url()
|
||||
|
||||
if avatar_url and request:
|
||||
if obj.avatar:
|
||||
return request.build_absolute_uri(avatar_url)
|
||||
else:
|
||||
return avatar_url
|
||||
return avatar_url
|
||||
```
|
||||
|
||||
### 4. 依赖安装
|
||||
|
||||
添加了必需的依赖:
|
||||
|
||||
```txt
|
||||
# requirements.txt
|
||||
Pillow==11.1.0 # Django ImageField所需
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 方式一:本地图片上传(推荐)
|
||||
|
||||
```python
|
||||
# 创建创作者并上传本地图片
|
||||
creator = CreatorProfile.objects.create(name="张三")
|
||||
|
||||
# 通过Django admin、表单或API上传图片
|
||||
with open('avatar.jpg', 'rb') as f:
|
||||
creator.avatar.save('zhang_san.jpg', f)
|
||||
|
||||
print(creator.get_avatar_url()) # 返回: /media/avatars/zhang_san.jpg
|
||||
```
|
||||
|
||||
### 方式二:外部URL(向后兼容)
|
||||
|
||||
```python
|
||||
# 使用外部URL
|
||||
creator = CreatorProfile.objects.create(
|
||||
name="李四",
|
||||
avatar_url="https://example.com/avatar.jpg"
|
||||
)
|
||||
|
||||
print(creator.get_avatar_url()) # 返回: https://example.com/avatar.jpg
|
||||
```
|
||||
|
||||
### 方式三:混合使用
|
||||
|
||||
```python
|
||||
# 既有外部URL又有本地图片时,优先使用本地图片
|
||||
creator = CreatorProfile.objects.create(
|
||||
name="王五",
|
||||
avatar_url="https://example.com/backup.jpg" # 备用URL
|
||||
)
|
||||
|
||||
# 后来上传了本地图片
|
||||
creator.avatar.save('wang_wu.jpg', image_file)
|
||||
|
||||
print(creator.get_avatar_url()) # 返回本地图片URL,不是外部URL
|
||||
```
|
||||
|
||||
## API使用
|
||||
|
||||
### 获取头像数据
|
||||
|
||||
```javascript
|
||||
// 前端JavaScript
|
||||
fetch('/api/daren_detail/creators/1/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// 使用avatar_display_url字段,自动选择最佳URL
|
||||
if (data.avatar_display_url) {
|
||||
document.getElementById('avatar').src = data.avatar_display_url;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 上传头像
|
||||
|
||||
```javascript
|
||||
// 上传本地图片
|
||||
function uploadAvatar(file, creatorId) {
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', file);
|
||||
|
||||
fetch(`/api/daren_detail/creators/${creatorId}/`, {
|
||||
method: 'PATCH',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('上传成功:', data.avatar_display_url);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
项目根目录/
|
||||
├── media/ # 媒体文件目录
|
||||
│ └── avatars/ # 头像存储目录
|
||||
│ ├── zhang_san.jpg
|
||||
│ └── wang_wu.jpg
|
||||
├── apps/daren_detail/
|
||||
│ ├── models.py # 包含avatar字段
|
||||
│ └── serializers.py # 新增的序列化器
|
||||
└── daren/
|
||||
├── settings.py # 媒体文件配置
|
||||
└── urls.py # 媒体文件URL配置
|
||||
```
|
||||
|
||||
## 数据库更改
|
||||
|
||||
已经执行的迁移:
|
||||
```bash
|
||||
python manage.py makemigrations daren_detail
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
新增数据库字段:
|
||||
- `avatar` (varchar): 存储本地图片路径,如 `avatars/zhang_san.jpg`
|
||||
|
||||
## 优势
|
||||
|
||||
1. **向后兼容**:原有的 `avatar_url` 字段继续工作
|
||||
2. **智能选择**:优先使用本地图片,降低外部依赖
|
||||
3. **性能优化**:本地图片加载更快
|
||||
4. **数据控制**:头像文件存储在自己的服务器上
|
||||
5. **灵活性**:支持两种方式同时存在
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **生产环境**:建议使用CDN或对象存储服务(如AWS S3)
|
||||
2. **图片优化**:可以添加图片压缩和缩放功能
|
||||
3. **存储限制**:注意设置合理的文件大小限制
|
||||
4. **安全性**:验证上传文件类型,防止恶意文件上传
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 在Django admin中为avatar字段添加图片预览
|
||||
2. 实现图片自动压缩和多尺寸生成
|
||||
3. 添加图片格式验证和文件大小限制
|
||||
4. 考虑使用django-imagekit进行高级图片处理
|
@ -1,3 +1,83 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from .models import CreatorProfile
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@admin.register(CreatorProfile)
|
||||
class CreatorProfileAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'avatar_thumbnail', 'name', 'category', 'e_commerce_level', 'exposure_level',
|
||||
'followers', 'gmv', 'items_sold', 'avg_video_views', 'pricing', 'collab_count',
|
||||
'mcn', 'location', 'create_time'
|
||||
)
|
||||
list_filter = (
|
||||
'category', 'e_commerce_level', 'exposure_level', 'mcn', 'location',
|
||||
'e_commerce_platforms', 'create_time', 'update_time'
|
||||
)
|
||||
search_fields = (
|
||||
'name', 'email', 'instagram', 'tiktok_link', 'location', 'mcn',
|
||||
'latest_collab', 'live_schedule', 'pricing_package'
|
||||
)
|
||||
readonly_fields = ('create_time', 'update_time', 'avatar_preview')
|
||||
|
||||
def avatar_thumbnail(self, obj):
|
||||
"""在列表中显示头像缩略图"""
|
||||
avatar_url = obj.get_avatar_url()
|
||||
if avatar_url:
|
||||
return format_html('<img src="{}" width="40" height="40" style="border-radius: 50%; object-fit: cover;" />', avatar_url)
|
||||
return "无头像"
|
||||
avatar_thumbnail.short_description = "头像"
|
||||
|
||||
def avatar_preview(self, obj):
|
||||
"""在详情页显示头像预览"""
|
||||
avatar_url = obj.get_avatar_url()
|
||||
if avatar_url:
|
||||
return format_html('<img src="{}" width="150" height="150" style="border-radius: 10px; object-fit: cover;" />', avatar_url)
|
||||
return "无头像"
|
||||
avatar_preview.short_description = "头像预览"
|
||||
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('name', 'avatar', 'avatar_url', 'avatar_preview')
|
||||
}),
|
||||
('联系方式', {
|
||||
'fields': ('email', 'instagram', 'tiktok_link', 'location', 'live_schedule')
|
||||
}),
|
||||
('分类等级', {
|
||||
'fields': ('category', 'e_commerce_level', 'exposure_level')
|
||||
}),
|
||||
('数据指标', {
|
||||
'fields': ('followers', 'gmv', 'items_sold', 'avg_video_views')
|
||||
}),
|
||||
('定价信息', {
|
||||
'fields': ('pricing', 'pricing_package')
|
||||
}),
|
||||
('合作信息', {
|
||||
'fields': ('collab_count', 'latest_collab', 'mcn')
|
||||
}),
|
||||
('电商平台', {
|
||||
'fields': ('e_commerce_platforms',)
|
||||
}),
|
||||
('分析数据', {
|
||||
'fields': ('gmv_by_channel', 'gmv_by_category'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('时间信息', {
|
||||
'fields': ('create_time', 'update_time'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
list_per_page = 20
|
||||
ordering = ('-create_time',)
|
||||
|
||||
# 添加批量操作
|
||||
actions = ['export_selected_creators']
|
||||
|
||||
def export_selected_creators(self, request, queryset):
|
||||
"""批量导出选中的达人信息"""
|
||||
# 这里可以添加导出逻辑
|
||||
self.message_user(request, f"已选择 {queryset.count()} 个达人进行导出")
|
||||
export_selected_creators.short_description = "导出选中的达人信息"
|
||||
|
1056
apps/daren_detail/daren_detail.md
Normal file
1056
apps/daren_detail/daren_detail.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-05-23 09:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('daren_detail', '0002_remove_creatorprofile_pricing_max_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='privatecreatorrelation',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('active', '活跃'), ('archived', '已归档'), ('favorite', '收藏'), ('public_removed', '公有库已移除')], default='active', max_length=20, verbose_name='状态'),
|
||||
),
|
||||
]
|
18
apps/daren_detail/migrations/0004_creatorprofile_avatar.py
Normal file
18
apps/daren_detail/migrations/0004_creatorprofile_avatar.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-05-23 09:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('daren_detail', '0003_alter_privatecreatorrelation_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='creatorprofile',
|
||||
name='avatar',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='avatars/', verbose_name='头像图片'),
|
||||
),
|
||||
]
|
@ -113,6 +113,8 @@ class CreatorProfile(models.Model):
|
||||
"""达人信息模型,用于筛选功能"""
|
||||
# 基本信息
|
||||
name = models.CharField(max_length=255, verbose_name="达人名称")
|
||||
# 修改为支持本地图片上传,同时保持URL兼容性
|
||||
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, verbose_name="头像图片")
|
||||
avatar_url = models.TextField(blank=True, null=True, verbose_name="头像URL")
|
||||
|
||||
# 新增联系方式
|
||||
@ -211,6 +213,14 @@ class CreatorProfile(models.Model):
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||||
|
||||
def get_avatar_url(self):
|
||||
"""获取头像URL,优先返回本地图片,其次返回外部URL"""
|
||||
if self.avatar:
|
||||
return self.avatar.url
|
||||
elif self.avatar_url:
|
||||
return self.avatar_url
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
verbose_name = "达人信息"
|
||||
verbose_name_plural = verbose_name
|
||||
@ -466,7 +476,8 @@ class PrivateCreatorRelation(models.Model):
|
||||
choices=[
|
||||
("active", "活跃"),
|
||||
("archived", "已归档"),
|
||||
("favorite", "收藏")
|
||||
("favorite", "收藏"),
|
||||
("public_removed", "公有库已移除")
|
||||
])
|
||||
|
||||
# 时间戳
|
||||
|
61
apps/daren_detail/serializers.py
Normal file
61
apps/daren_detail/serializers.py
Normal file
@ -0,0 +1,61 @@
|
||||
from rest_framework import serializers
|
||||
from .models import CreatorProfile
|
||||
|
||||
|
||||
class CreatorProfileSerializer(serializers.ModelSerializer):
|
||||
"""创作者资料序列化器,包含头像处理"""
|
||||
avatar_display_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CreatorProfile
|
||||
fields = [
|
||||
'id', 'name', 'avatar', 'avatar_url', 'avatar_display_url',
|
||||
'email', 'instagram', 'tiktok_link', 'location', 'live_schedule',
|
||||
'category', 'e_commerce_level', 'exposure_level', 'followers',
|
||||
'gmv', 'items_sold', 'avg_video_views', 'pricing', 'pricing_package',
|
||||
'collab_count', 'latest_collab', 'e_commerce_platforms',
|
||||
'gmv_by_channel', 'gmv_by_category', 'mcn',
|
||||
'create_time', 'update_time'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'avatar': {'write_only': False}, # 允许读写
|
||||
'avatar_url': {'write_only': False} # 允许读写
|
||||
}
|
||||
|
||||
def get_avatar_display_url(self, obj):
|
||||
"""获取头像显示URL,优先使用本地图片"""
|
||||
request = self.context.get('request')
|
||||
avatar_url = obj.get_avatar_url()
|
||||
|
||||
if avatar_url and request:
|
||||
# 如果是本地图片,返回完整的URL
|
||||
if obj.avatar:
|
||||
return request.build_absolute_uri(avatar_url)
|
||||
# 如果是外部URL,直接返回
|
||||
else:
|
||||
return avatar_url
|
||||
return avatar_url
|
||||
|
||||
|
||||
class CreatorProfileListSerializer(serializers.ModelSerializer):
|
||||
"""创作者资料列表序列化器,用于列表显示,字段较少"""
|
||||
avatar_display_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CreatorProfile
|
||||
fields = [
|
||||
'id', 'name', 'avatar_display_url', 'category',
|
||||
'exposure_level', 'followers', 'gmv', 'mcn'
|
||||
]
|
||||
|
||||
def get_avatar_display_url(self, obj):
|
||||
"""获取头像显示URL"""
|
||||
request = self.context.get('request')
|
||||
avatar_url = obj.get_avatar_url()
|
||||
|
||||
if avatar_url and request:
|
||||
if obj.avatar:
|
||||
return request.build_absolute_uri(avatar_url)
|
||||
else:
|
||||
return avatar_url
|
||||
return avatar_url
|
@ -1,74 +1,75 @@
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
# 添加一个简单的index视图函数
|
||||
# def index(request):
|
||||
# return HttpResponse("Welcome to TikTok Videos Analysis API")
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# path('', index, name='index'),
|
||||
# # TikTok API路由 - 旧路径保持不变以保证兼容性
|
||||
# path('tiktok/user-videos/', views.get_tiktok_user_videos, name='get-tiktok-user-videos'),
|
||||
# path('tiktok/fetch_videos/', views.fetch_tiktok_videos, name='fetch_tiktok_videos'),
|
||||
# path('tiktok/delete_user/', views.delete_tiktok_user, name='delete_tiktok_user'),
|
||||
|
||||
|
||||
|
||||
# 新的API路由结构 - 只保留TikTok API
|
||||
# path('api/tiktok/', include('app.api.tiktok.urls')), # TikTok API
|
||||
|
||||
# 添加Creator相关API
|
||||
path('creators/filter/', views.filter_creators, name='filter_creators'),
|
||||
path('creators/add/', views.add_creator, name='add_creator'),
|
||||
path('creators/<int:creator_id>/', views.get_creator_detail, name='get_creator_detail'),
|
||||
path('creators/update_detail/', views.update_creator_detail, name='update_creator_detail'),
|
||||
path('creators/<int:creator_id>/campaigns/', views.get_creator_brand_campaigns, name='get_creator_brand_campaigns'),
|
||||
|
||||
# 添加Campaign相关API
|
||||
path('campaigns/', views.get_campaigns, name='get_campaigns'),
|
||||
path('campaigns/add/', views.add_to_campaign, name='add_to_campaign'),
|
||||
|
||||
# 新增的指标相关API
|
||||
path('creators/<int:creator_id>/metrics/', views.get_creator_metrics, name='get_creator_metrics'),
|
||||
# 获取创作者的所有指标数据
|
||||
path('creators/metrics/update/', views.update_creator_metrics, name='update_creator_metrics'), # 更新创作者的指标数据
|
||||
|
||||
# 添加粉丝统计和趋势数据相关的路由
|
||||
path('creator/<int:creator_id>/followers/', views.get_creator_followers_metrics, name='get_creator_followers'),
|
||||
path('creator/followers/', views.get_creator_followers_metrics, name='get_creator_followers_query'),
|
||||
|
||||
# 获取创作者趋势数据
|
||||
path('creator/<int:creator_id>/trends/', views.get_creator_trends, name='get_creator_trends'),
|
||||
path('creator/trends/', views.get_creator_trends, name='get_creator_trends_query'),
|
||||
|
||||
# 更新创作者粉丝统计数据
|
||||
path('creator/followers/update/', views.update_creator_followers, name='update_creator_followers'),
|
||||
|
||||
# 更新创作者趋势数据
|
||||
path('creator/trend/update/', views.update_creator_trend, name='update_creator_trend'),
|
||||
|
||||
# 添加创作者视频相关的路由
|
||||
path('creator/<int:creator_id>/videos/', views.get_creator_videos, name='get_creator_videos'),
|
||||
path('creator/videos/', views.get_creator_videos, name='get_creator_videos_query'),
|
||||
|
||||
# 添加创作者视频
|
||||
path('creator/video/add/', views.add_creator_video, name='add_creator_video'),
|
||||
|
||||
# 公有达人和私有达人API
|
||||
path('public/creators/', views.get_public_creators, name='get_public_creators'),
|
||||
path('public/creators/filter/', views.filter_public_creators, name='filter_public_creators'),
|
||||
path('public/creators/add/', views.add_to_public_pool, name='add_to_public_pool'),
|
||||
|
||||
# 私有达人库
|
||||
path('private/pools/', views.get_private_pools, name='get_private_pools'),
|
||||
path('private/pools/create/', views.create_private_pool, name='create_private_pool'),
|
||||
path('private/pools/creators/<int:pool_id>/', views.get_private_pool_creators, name='get_private_pool_creators'),
|
||||
path('private/pools/creators/', views.get_private_pool_creators, name='get_private_pool_creators_no_id'),
|
||||
path('private/pools/creators/add/', views.add_creator_to_private_pool, name='add_creator_to_private_pool'),
|
||||
path('private/pools/creators/update/', views.update_creator_in_private_pool, name='update_creator_in_private_pool'),
|
||||
path('private/pools/creators/remove/', views.remove_creator_from_private_pool, name='remove_creator_from_private_pool'),
|
||||
path('private/pools/creators/filter/', views.filter_private_pool_creators, name='filter_private_pool_creators'),
|
||||
]
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
# 添加一个简单的index视图函数
|
||||
# def index(request):
|
||||
# return HttpResponse("Welcome to TikTok Videos Analysis API")
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# path('', index, name='index'),
|
||||
# # TikTok API路由 - 旧路径保持不变以保证兼容性
|
||||
# path('tiktok/user-videos/', views.get_tiktok_user_videos, name='get-tiktok-user-videos'),
|
||||
# path('tiktok/fetch_videos/', views.fetch_tiktok_videos, name='fetch_tiktok_videos'),
|
||||
# path('tiktok/delete_user/', views.delete_tiktok_user, name='delete_tiktok_user'),
|
||||
|
||||
|
||||
|
||||
# 新的API路由结构 - 只保留TikTok API
|
||||
# path('api/tiktok/', include('app.api.tiktok.urls')), # TikTok API
|
||||
|
||||
# 添加Creator相关API
|
||||
path('creators/filter/', views.filter_creators, name='filter_creators'),
|
||||
path('creators/add/', views.add_creator, name='add_creator'),
|
||||
path('creators/<int:creator_id>/', views.get_creator_detail, name='get_creator_detail'),
|
||||
path('creators/update_detail/', views.update_creator_detail, name='update_creator_detail'),
|
||||
path('creators/<int:creator_id>/campaigns/', views.get_creator_brand_campaigns, name='get_creator_brand_campaigns'),
|
||||
|
||||
# 添加Campaign相关API
|
||||
path('campaigns/', views.get_campaigns, name='get_campaigns'),
|
||||
path('campaigns/add/', views.add_to_campaign, name='add_to_campaign'),
|
||||
|
||||
# 新增的指标相关API
|
||||
path('creators/<int:creator_id>/metrics/', views.get_creator_metrics, name='get_creator_metrics'),
|
||||
# 获取创作者的所有指标数据
|
||||
path('creators/metrics/update/', views.update_creator_metrics, name='update_creator_metrics'), # 更新创作者的指标数据
|
||||
|
||||
# 添加粉丝统计和趋势数据相关的路由
|
||||
path('creator/<int:creator_id>/followers/', views.get_creator_followers_metrics, name='get_creator_followers'),
|
||||
path('creator/followers/', views.get_creator_followers_metrics, name='get_creator_followers_query'),
|
||||
|
||||
# 获取创作者趋势数据
|
||||
path('creator/<int:creator_id>/trends/', views.get_creator_trends, name='get_creator_trends'),
|
||||
path('creator/trends/', views.get_creator_trends, name='get_creator_trends_query'),
|
||||
|
||||
# 更新创作者粉丝统计数据
|
||||
path('creator/followers/update/', views.update_creator_followers, name='update_creator_followers'),
|
||||
|
||||
# 更新创作者趋势数据
|
||||
path('creator/trend/update/', views.update_creator_trend, name='update_creator_trend'),
|
||||
|
||||
# 添加创作者视频相关的路由
|
||||
path('creator/<int:creator_id>/videos/', views.get_creator_videos, name='get_creator_videos'),
|
||||
path('creator/videos/', views.get_creator_videos, name='get_creator_videos_query'),
|
||||
|
||||
# 添加创作者视频
|
||||
path('creator/video/add/', views.add_creator_video, name='add_creator_video'),
|
||||
|
||||
# 公有达人和私有达人API
|
||||
path('public/creators/', views.get_public_creators, name='get_public_creators'),
|
||||
path('public/creators/filter/', views.filter_public_creators, name='filter_public_creators'),
|
||||
path('public/creators/add/', views.add_to_public_pool, name='add_to_public_pool'),
|
||||
path('public/creators/remove/', views.remove_from_public_pool, name='remove_from_public_pool'),
|
||||
|
||||
# 私有达人库
|
||||
path('private/pools/', views.get_private_pools, name='get_private_pools'),
|
||||
path('private/pools/create/', views.create_private_pool, name='create_private_pool'),
|
||||
path('private/pools/creators/<int:pool_id>/', views.get_private_pool_creators, name='get_private_pool_creators'),
|
||||
path('private/pools/creators/', views.get_private_pool_creators, name='get_private_pool_creators_no_id'),
|
||||
path('private/pools/creators/add/', views.add_creator_to_private_pool, name='add_creator_to_private_pool'),
|
||||
path('private/pools/creators/update/', views.update_creator_in_private_pool, name='update_creator_in_private_pool'),
|
||||
path('private/pools/creators/remove/', views.remove_creator_from_private_pool, name='remove_creator_from_private_pool'),
|
||||
path('private/pools/creators/filter/', views.filter_private_pool_creators, name='filter_private_pool_creators'),
|
||||
]
|
||||
|
@ -1312,19 +1312,25 @@ def get_creator_followers_metrics(request, creator_id=None):
|
||||
}
|
||||
else:
|
||||
# 构建粉丝统计数据
|
||||
# 处理locations数据,对每个值保留两位小数
|
||||
processed_locations = {}
|
||||
if follower_metrics.location_data:
|
||||
for location, value in follower_metrics.location_data.items():
|
||||
processed_locations[location] = round(float(value), 2)
|
||||
|
||||
follower_data = {
|
||||
'gender': {
|
||||
'female': follower_metrics.female_percentage,
|
||||
'male': follower_metrics.male_percentage
|
||||
'female': round(follower_metrics.female_percentage, 2),
|
||||
'male': round(follower_metrics.male_percentage, 2)
|
||||
},
|
||||
'age': {
|
||||
'18-24': follower_metrics.age_18_24_percentage,
|
||||
'25-34': follower_metrics.age_25_34_percentage,
|
||||
'35-44': follower_metrics.age_35_44_percentage,
|
||||
'45-54': follower_metrics.age_45_54_percentage,
|
||||
'55+': follower_metrics.age_55_plus_percentage
|
||||
'18-24': round(follower_metrics.age_18_24_percentage, 2),
|
||||
'25-34': round(follower_metrics.age_25_34_percentage, 2),
|
||||
'35-44': round(follower_metrics.age_35_44_percentage, 2),
|
||||
'45-54': round(follower_metrics.age_45_54_percentage, 2),
|
||||
'55+': round(follower_metrics.age_55_plus_percentage, 2)
|
||||
},
|
||||
'locations': follower_metrics.location_data,
|
||||
'locations': processed_locations,
|
||||
'date_range': {
|
||||
'start_date': follower_metrics.start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': follower_metrics.end_date.strftime('%Y-%m-%d')
|
||||
@ -1441,12 +1447,12 @@ def get_creator_trends(request, creator_id=None):
|
||||
views_value = int(base_views + random.uniform(-base_views * 0.2, base_views * 0.4))
|
||||
engagement_value = base_engagement + random.uniform(-base_engagement * 0.1, base_engagement * 0.15)
|
||||
|
||||
# 确保值不小于0
|
||||
trend_data['gmv'].append(max(0, gmv_value))
|
||||
# 确保值不小于0,并对浮点数保留两位小数
|
||||
trend_data['gmv'].append(round(max(0, gmv_value), 2))
|
||||
trend_data['items_sold'].append(max(0, items_value))
|
||||
trend_data['followers'].append(max(0, followers_value))
|
||||
trend_data['video_views'].append(max(0, views_value))
|
||||
trend_data['engagement_rate'].append(max(0, engagement_value))
|
||||
trend_data['engagement_rate'].append(round(max(0, engagement_value), 2))
|
||||
|
||||
# 更新基准值,使数据有一定的连续性
|
||||
base_gmv = gmv_value
|
||||
@ -1471,11 +1477,11 @@ def get_creator_trends(request, creator_id=None):
|
||||
}
|
||||
|
||||
for trend in trends:
|
||||
trend_data['gmv'].append(trend.gmv)
|
||||
trend_data['gmv'].append(round(trend.gmv, 2))
|
||||
trend_data['items_sold'].append(trend.items_sold)
|
||||
trend_data['followers'].append(trend.followers_count)
|
||||
trend_data['video_views'].append(trend.video_views)
|
||||
trend_data['engagement_rate'].append(trend.engagement_rate)
|
||||
trend_data['engagement_rate'].append(round(trend.engagement_rate, 2))
|
||||
trend_data['dates'].append(trend.date.strftime('%Y-%m-%d'))
|
||||
|
||||
trend_data['date_range'] = {
|
||||
@ -2486,7 +2492,9 @@ def get_private_pool_creators(request, pool_id=None):
|
||||
"notes": relation.notes,
|
||||
"status": relation.status,
|
||||
"added_from_public": relation.added_from_public,
|
||||
"added_at": relation.created_at.strftime('%Y-%m-%d')
|
||||
"added_at": relation.created_at.strftime('%Y-%m-%d'),
|
||||
"is_public_removed": relation.status == 'public_removed', # 添加公有库移除标识
|
||||
"status_note": "该达人已从公有库中移除" if relation.status == 'public_removed' else None # 状态说明
|
||||
}
|
||||
creator_list.append(formatted_creator)
|
||||
|
||||
@ -3234,12 +3242,13 @@ def filter_private_pool_creators(request):
|
||||
"gmv": gmv_formatted,
|
||||
"avg_video_views": avg_views_formatted,
|
||||
"pricing": pricing_range, # 使用格式化后的价格区间
|
||||
"pricing_package": creator.pricing_package,
|
||||
"collab_count": creator.collab_count,
|
||||
"notes": relation.notes,
|
||||
"status": relation.status,
|
||||
"added_from_public": relation.added_from_public,
|
||||
"added_at": relation.created_at.strftime('%Y-%m-%d')
|
||||
"added_at": relation.created_at.strftime('%Y-%m-%d'),
|
||||
"is_public_removed": relation.status == 'public_removed', # 添加公有库移除标识
|
||||
"status_note": "该达人已从公有库中移除" if relation.status == 'public_removed' else None # 状态说明
|
||||
}
|
||||
creator_list.append(formatted_creator)
|
||||
|
||||
@ -3284,6 +3293,89 @@ def filter_private_pool_creators(request):
|
||||
}, json_dumps_params={'ensure_ascii': False})
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@authentication_classes([CustomTokenAuthentication])
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def remove_from_public_pool(request):
|
||||
"""从公有达人库中移除达人,同时更新私有库中相关记录的状态"""
|
||||
try:
|
||||
from .models import PublicCreatorPool, PrivateCreatorRelation, CreatorProfile
|
||||
import json
|
||||
|
||||
data = json.loads(request.body)
|
||||
|
||||
# 获取必要参数
|
||||
creator_id = data.get('creator_id')
|
||||
public_id = data.get('public_id') # 也可以通过public_id删除
|
||||
|
||||
if not creator_id and not public_id:
|
||||
return JsonResponse({
|
||||
'code': 400,
|
||||
'message': '缺少必要参数: creator_id 或 public_id',
|
||||
'data': None
|
||||
}, json_dumps_params={'ensure_ascii': False})
|
||||
|
||||
# 查询公有库记录
|
||||
try:
|
||||
if public_id:
|
||||
public_creator = PublicCreatorPool.objects.get(id=public_id)
|
||||
creator = public_creator.creator
|
||||
else:
|
||||
creator = CreatorProfile.objects.get(id=creator_id)
|
||||
public_creator = PublicCreatorPool.objects.get(creator=creator)
|
||||
|
||||
except (PublicCreatorPool.DoesNotExist, CreatorProfile.DoesNotExist):
|
||||
return JsonResponse({
|
||||
'code': 404,
|
||||
'message': '找不到指定的公有库达人记录',
|
||||
'data': None
|
||||
}, json_dumps_params={'ensure_ascii': False})
|
||||
|
||||
# 查找所有私有库中引用此达人且标记为从公有库添加的记录
|
||||
private_relations = PrivateCreatorRelation.objects.filter(
|
||||
creator=creator,
|
||||
added_from_public=True,
|
||||
status='active' # 只更新活跃状态的记录
|
||||
)
|
||||
|
||||
# 更新私有库中相关记录的状态为"已失效"
|
||||
updated_private_count = 0
|
||||
if private_relations.exists():
|
||||
# 为了兼容性,可以选择添加新的状态或使用现有的archived状态
|
||||
# 这里我们添加一个新的状态值来明确表示"公有库已移除"
|
||||
updated_private_count = private_relations.update(
|
||||
status='public_removed' # 新增状态:公有库已移除
|
||||
)
|
||||
|
||||
# 删除公有库记录
|
||||
public_creator.delete()
|
||||
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '成功从公有库移除达人',
|
||||
'data': {
|
||||
'creator': {
|
||||
'id': creator.id,
|
||||
'name': creator.name
|
||||
},
|
||||
'removed_from_public': True,
|
||||
'updated_private_relations': updated_private_count,
|
||||
'note': f'已更新 {updated_private_count} 个私有库中的相关记录状态'
|
||||
}
|
||||
}, json_dumps_params={'ensure_ascii': False})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"从公有库移除达人失败: {e}")
|
||||
import traceback
|
||||
logger.error(f"详细错误: {traceback.format_exc()}")
|
||||
return JsonResponse({
|
||||
'code': 500,
|
||||
'message': f'从公有库移除达人失败: {str(e)}',
|
||||
'data': None
|
||||
}, json_dumps_params={'ensure_ascii': False})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
18
apps/user/migrations/0005_user_is_superuser.py
Normal file
18
apps/user/migrations/0005_user_is_superuser.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-05-23 09:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0004_user_is_active_user_is_staff'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='is_superuser',
|
||||
field=models.BooleanField(default=False, verbose_name='超级用户状态'),
|
||||
),
|
||||
]
|
@ -14,6 +14,17 @@ class UserManager(BaseUserManager):
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault('is_staff', True)
|
||||
extra_fields.setdefault('is_superuser', True)
|
||||
|
||||
if extra_fields.get('is_staff') is not True:
|
||||
raise ValueError('超级用户必须设置is_staff=True')
|
||||
if extra_fields.get('is_superuser') is not True:
|
||||
raise ValueError('超级用户必须设置is_superuser=True')
|
||||
|
||||
return self.create_user(email, password, **extra_fields)
|
||||
|
||||
class User(AbstractBaseUser):
|
||||
"""用户模型,用于登录和账户管理"""
|
||||
email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱")
|
||||
@ -24,6 +35,7 @@ class User(AbstractBaseUser):
|
||||
last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间")
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False, verbose_name="超级用户状态")
|
||||
|
||||
# 时间戳
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
@ -46,6 +58,14 @@ class User(AbstractBaseUser):
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
"""用户是否有特定权限"""
|
||||
return self.is_superuser
|
||||
|
||||
def has_module_perms(self, app_label):
|
||||
"""用户是否有访问特定app的权限"""
|
||||
return self.is_superuser
|
||||
|
||||
class UserToken(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tokens')
|
||||
token = models.CharField(max_length=40, unique=True)
|
||||
|
@ -207,6 +207,10 @@ LOGGING = {
|
||||
# 自定义用户模型
|
||||
AUTH_USER_MODEL = 'user.User'
|
||||
|
||||
# 媒体文件配置
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
||||
# REST Framework 设置
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [], # 默认不需要认证
|
||||
|
@ -4,6 +4,8 @@ from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
@ -16,3 +18,7 @@ urlpatterns = [
|
||||
path('api/template/', include('apps.template.urls')),
|
||||
path('api/', include('apps.brands.urls')),
|
||||
]
|
||||
|
||||
# 在开发环境中提供媒体文件服务
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
153
example_usage.py
Normal file
153
example_usage.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""
|
||||
CreatorProfile头像字段使用示例
|
||||
|
||||
演示如何在Django项目中使用avatar字段来处理本地图片和外部URL
|
||||
"""
|
||||
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from apps.daren_detail.models import CreatorProfile
|
||||
from django.conf import settings
|
||||
import os
|
||||
|
||||
def create_creator_with_local_avatar():
|
||||
"""创建一个使用本地图片作为头像的创作者"""
|
||||
|
||||
# 方式1:通过Django admin或表单上传图片
|
||||
# 用户上传图片后,Django会自动保存到 MEDIA_ROOT/avatars/ 目录
|
||||
creator = CreatorProfile.objects.create(
|
||||
name="张三",
|
||||
category="Beauty & Personal Care",
|
||||
followers=10000
|
||||
)
|
||||
|
||||
# 假设有一个图片文件
|
||||
# with open('path/to/image.jpg', 'rb') as f:
|
||||
# creator.avatar.save('zhang_san.jpg', f)
|
||||
|
||||
print(f"创作者: {creator.name}")
|
||||
print(f"头像URL: {creator.get_avatar_url()}")
|
||||
|
||||
return creator
|
||||
|
||||
def create_creator_with_external_url():
|
||||
"""创建一个使用外部URL作为头像的创作者"""
|
||||
|
||||
creator = CreatorProfile.objects.create(
|
||||
name="李四",
|
||||
avatar_url="https://example.com/avatar.jpg",
|
||||
category="Fashion Accessories",
|
||||
followers=20000
|
||||
)
|
||||
|
||||
print(f"创作者: {creator.name}")
|
||||
print(f"头像URL: {creator.get_avatar_url()}")
|
||||
|
||||
return creator
|
||||
|
||||
def create_creator_with_both():
|
||||
"""创建一个既有本地图片又有外部URL的创作者(优先使用本地图片)"""
|
||||
|
||||
creator = CreatorProfile.objects.create(
|
||||
name="王五",
|
||||
avatar_url="https://example.com/backup-avatar.jpg",
|
||||
category="Sports & Outdoor",
|
||||
followers=30000
|
||||
)
|
||||
|
||||
# 如果后来上传了本地图片,会优先使用本地图片
|
||||
# creator.avatar.save('wang_wu.jpg', image_file)
|
||||
|
||||
print(f"创作者: {creator.name}")
|
||||
print(f"头像URL: {creator.get_avatar_url()}")
|
||||
|
||||
return creator
|
||||
|
||||
# API使用示例
|
||||
def api_response_example():
|
||||
"""API响应示例"""
|
||||
|
||||
# 在视图中使用序列化器
|
||||
from apps.daren_detail.serializers import CreatorProfileSerializer
|
||||
from django.http import HttpRequest
|
||||
|
||||
creator = CreatorProfile.objects.first()
|
||||
|
||||
# 创建一个模拟的request对象
|
||||
request = HttpRequest()
|
||||
request.META['HTTP_HOST'] = 'localhost:8000'
|
||||
request.META['wsgi.url_scheme'] = 'http'
|
||||
|
||||
serializer = CreatorProfileSerializer(creator, context={'request': request})
|
||||
data = serializer.data
|
||||
|
||||
print("API响应示例:")
|
||||
print(f"名称: {data['name']}")
|
||||
print(f"本地头像字段: {data.get('avatar')}")
|
||||
print(f"外部头像URL: {data.get('avatar_url')}")
|
||||
print(f"实际显示URL: {data.get('avatar_display_url')}")
|
||||
|
||||
# 前端使用示例(JavaScript)
|
||||
frontend_example = """
|
||||
// 前端JavaScript使用示例
|
||||
|
||||
// 获取创作者数据
|
||||
fetch('/api/daren_detail/creators/1/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// 显示头像
|
||||
const avatarImg = document.getElementById('avatar');
|
||||
|
||||
// 使用avatar_display_url字段,它会自动选择合适的URL
|
||||
if (data.avatar_display_url) {
|
||||
avatarImg.src = data.avatar_display_url;
|
||||
avatarImg.style.display = 'block';
|
||||
} else {
|
||||
// 如果没有头像,显示默认头像
|
||||
avatarImg.src = '/static/images/default-avatar.png';
|
||||
}
|
||||
|
||||
// 或者可以检查具体的字段类型
|
||||
if (data.avatar) {
|
||||
// 这是本地上传的图片
|
||||
console.log('使用本地头像:', data.avatar);
|
||||
} else if (data.avatar_url) {
|
||||
// 这是外部URL
|
||||
console.log('使用外部头像:', data.avatar_url);
|
||||
}
|
||||
});
|
||||
|
||||
// 上传头像示例
|
||||
function uploadAvatar(file, creatorId) {
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', file);
|
||||
|
||||
fetch(`/api/daren_detail/creators/${creatorId}/`, {
|
||||
method: 'PATCH',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken') // CSRF令牌
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('头像上传成功:', data.avatar_display_url);
|
||||
// 更新页面上的头像显示
|
||||
document.getElementById('avatar').src = data.avatar_display_url;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('头像上传失败:', error);
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("CreatorProfile头像字段使用示例")
|
||||
print("="*50)
|
||||
print()
|
||||
print("1. 支持本地图片上传到 media/avatars/ 目录")
|
||||
print("2. 支持外部URL链接")
|
||||
print("3. get_avatar_url() 方法优先返回本地图片URL")
|
||||
print("4. 序列化器提供avatar_display_url字段用于前端显示")
|
||||
print()
|
||||
print("前端JavaScript示例:")
|
||||
print(frontend_example)
|
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
117
test_avatar_display.py
Normal file
117
test_avatar_display.py
Normal file
@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
测试达人头像显示功能
|
||||
演示如何使用本地图片和外部URL
|
||||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
|
||||
# 设置Django环境
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'daren.settings')
|
||||
django.setup()
|
||||
|
||||
from apps.daren_detail.models import CreatorProfile
|
||||
from django.core.files import File
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
def test_avatar_display():
|
||||
"""测试头像显示功能"""
|
||||
print("=== 达人头像显示功能测试 ===\n")
|
||||
|
||||
# 1. 查询现有达人
|
||||
creators = CreatorProfile.objects.all()[:3]
|
||||
|
||||
print("1. 现有达人头像状态:")
|
||||
for creator in creators:
|
||||
avatar_url = creator.get_avatar_url()
|
||||
has_local = bool(creator.avatar)
|
||||
has_external = bool(creator.avatar_url)
|
||||
|
||||
print(f" - {creator.name}:")
|
||||
print(f" 本地图片: {'✓' if has_local else '✗'}")
|
||||
print(f" 外部URL: {'✓' if has_external else '✗'}")
|
||||
print(f" 显示URL: {avatar_url or '无'}")
|
||||
print()
|
||||
|
||||
# 2. 演示URL访问方式
|
||||
print("2. 头像URL访问示例:")
|
||||
for creator in creators:
|
||||
avatar_url = creator.get_avatar_url()
|
||||
if avatar_url:
|
||||
if creator.avatar:
|
||||
print(f" 本地图片: http://localhost:8000{avatar_url}")
|
||||
else:
|
||||
print(f" 外部URL: {avatar_url}")
|
||||
else:
|
||||
print(f" {creator.name}: 无头像")
|
||||
print()
|
||||
|
||||
# 3. 创建测试数据示例
|
||||
print("3. 创建测试达人示例:")
|
||||
|
||||
# 示例1:仅外部URL
|
||||
creator1, created = CreatorProfile.objects.get_or_create(
|
||||
name="测试达人A",
|
||||
defaults={
|
||||
'avatar_url': 'https://example.com/avatar1.jpg',
|
||||
'category': 'Beauty & Personal Care',
|
||||
'followers': 1000
|
||||
}
|
||||
)
|
||||
print(f" - {creator1.name}: {creator1.get_avatar_url()}")
|
||||
|
||||
# 示例2:仅本地图片(如果存在的话)
|
||||
existing_avatar = CreatorProfile.objects.filter(avatar__isnull=False).first()
|
||||
if existing_avatar:
|
||||
print(f" - {existing_avatar.name}: {existing_avatar.get_avatar_url()}")
|
||||
|
||||
print("\n=== 前端使用示例代码 ===")
|
||||
print("""
|
||||
// JavaScript: 获取并显示头像
|
||||
fetch('/api/daren_detail/creators/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
data.results.forEach(creator => {
|
||||
if (creator.avatar_display_url) {
|
||||
console.log(`${creator.name}: ${creator.avatar_display_url}`);
|
||||
|
||||
// 创建图片元素
|
||||
const img = document.createElement('img');
|
||||
img.src = creator.avatar_display_url;
|
||||
img.alt = `${creator.name}的头像`;
|
||||
img.className = 'creator-avatar';
|
||||
|
||||
// 添加到页面
|
||||
document.getElementById('creators-list').appendChild(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
""")
|
||||
|
||||
print("\n=== HTML模板使用示例 ===")
|
||||
print("""
|
||||
<!-- Django模板中使用 -->
|
||||
{% for creator in creators %}
|
||||
<div class="creator-card">
|
||||
<h3>{{ creator.name }}</h3>
|
||||
{% if creator.get_avatar_url %}
|
||||
<img src="{{ creator.get_avatar_url }}"
|
||||
alt="{{ creator.name }}的头像"
|
||||
class="avatar">
|
||||
{% else %}
|
||||
<div class="no-avatar">暂无头像</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_avatar_display()
|
||||
except Exception as e:
|
||||
print(f"测试出错: {e}")
|
||||
print("请确保:")
|
||||
print("1. 已运行 python manage.py migrate")
|
||||
print("2. 数据库中有达人数据")
|
||||
print("3. media/avatars/ 目录存在")
|
Loading…
Reference in New Issue
Block a user