forum
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -311,9 +311,9 @@ class OrderAdmin(ModelAdmin):
|
||||
|
||||
@admin.register(WeChatUser)
|
||||
class WeChatUserAdmin(ModelAdmin):
|
||||
list_display = ('nickname', 'avatar_display', 'gender_display', 'province', 'city', 'created_at')
|
||||
list_display = ('nickname', 'is_star', 'title', 'avatar_display', 'gender_display', 'province', 'city', 'created_at')
|
||||
search_fields = ('nickname', 'openid')
|
||||
list_filter = ('gender', 'province', 'city', 'created_at')
|
||||
list_filter = ('is_star', 'gender', 'province', 'city', 'created_at')
|
||||
readonly_fields = ('openid', 'unionid', 'session_key', 'created_at', 'updated_at')
|
||||
|
||||
def avatar_display(self, obj):
|
||||
@@ -331,6 +331,10 @@ class WeChatUserAdmin(ModelAdmin):
|
||||
('基本信息', {
|
||||
'fields': ('user', 'nickname', 'avatar_url', 'gender')
|
||||
}),
|
||||
('专家认证', {
|
||||
'fields': ('is_star', 'title'),
|
||||
'description': '标记为明星技术用户/专家,将在社区中展示'
|
||||
}),
|
||||
('位置信息', {
|
||||
'fields': ('country', 'province', 'city')
|
||||
}),
|
||||
|
||||
@@ -4,7 +4,7 @@ from .views import (
|
||||
ESP32ConfigViewSet, OrderViewSet, order_check_view,
|
||||
ServiceViewSet, VCCourseViewSet, ServiceOrderViewSet,
|
||||
payment_finish, pay, send_sms_code, wechat_login, update_user_info, DistributorViewSet,
|
||||
CourseEnrollmentViewSet, phone_login, bind_phone
|
||||
CourseEnrollmentViewSet, phone_login, bind_phone, WeChatUserViewSet, upload_image
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
@@ -15,6 +15,7 @@ router.register(r'courses', VCCourseViewSet)
|
||||
router.register(r'course-enrollments', CourseEnrollmentViewSet)
|
||||
router.register(r'service-orders', ServiceOrderViewSet)
|
||||
router.register(r'distributor', DistributorViewSet, basename='distributor')
|
||||
router.register(r'users', WeChatUserViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^finish/?$', payment_finish, name='payment-finish'),
|
||||
@@ -24,6 +25,7 @@ urlpatterns = [
|
||||
path('auth/phone-login/', phone_login, name='phone-login'),
|
||||
path('auth/bind-phone/', bind_phone, name='bind-phone'),
|
||||
path('wechat/update/', update_user_info, name='wechat-update'),
|
||||
path('upload/image/', upload_image, name='upload-image'),
|
||||
path('page/check-order/', order_check_view, name='check-order-page'),
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action, api_view
|
||||
from rest_framework.decorators import action, api_view, parser_classes
|
||||
from rest_framework.parsers import MultiPartParser, FormParser
|
||||
from rest_framework.response import Response
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.base import ContentFile
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
@@ -1309,3 +1312,95 @@ class DistributorViewSet(viewsets.GenericViewSet):
|
||||
serializer = OrderSerializer(orders, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
class WeChatUserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
微信用户视图集
|
||||
"""
|
||||
queryset = WeChatUser.objects.all()
|
||||
serializer_class = WeChatUserSerializer
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def stars(self, request):
|
||||
"""
|
||||
获取明星技术用户列表
|
||||
"""
|
||||
stars = WeChatUser.objects.filter(is_star=True).order_by('-created_at')
|
||||
serializer = self.get_serializer(stars, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='paid-items')
|
||||
def paid_items(self, request):
|
||||
"""
|
||||
获取当前用户已购买的项目(硬件、课程、服务)
|
||||
用于论坛发帖时关联
|
||||
"""
|
||||
user = get_current_wechat_user(request)
|
||||
if not user:
|
||||
return Response({'error': 'Unauthorized'}, status=401)
|
||||
|
||||
# 1. 硬件 (ESP32Config)
|
||||
paid_orders = Order.objects.filter(wechat_user=user, status__in=['paid', 'shipped'])
|
||||
config_ids = paid_orders.exclude(config__isnull=True).values_list('config_id', flat=True).distinct()
|
||||
configs = ESP32Config.objects.filter(id__in=config_ids)
|
||||
|
||||
# 2. 课程 (VCCourse)
|
||||
course_ids = paid_orders.exclude(course__isnull=True).values_list('course_id', flat=True).distinct()
|
||||
courses = VCCourse.objects.filter(id__in=course_ids)
|
||||
|
||||
# 3. 服务 (Service)
|
||||
# 暂时没有强关联 WeChatUser 的 ServiceOrder,如果有 phone_number 匹配逻辑可在此添加
|
||||
# 简单起见,暂不返回服务,或基于 phone_number 匹配
|
||||
service_orders = ServiceOrder.objects.filter(phone_number=user.phone_number, status='paid')
|
||||
service_ids = service_orders.values_list('service_id', flat=True).distinct()
|
||||
services = Service.objects.filter(id__in=service_ids)
|
||||
|
||||
return Response({
|
||||
'configs': ESP32ConfigSerializer(configs, many=True).data,
|
||||
'courses': VCCourseSerializer(courses, many=True).data,
|
||||
'services': ServiceSerializer(services, many=True).data
|
||||
})
|
||||
|
||||
@extend_schema(
|
||||
summary="上传图片",
|
||||
description="上传图片文件,返回图片URL",
|
||||
request={
|
||||
'multipart/form-data': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'file': {'type': 'string', 'format': 'binary'}
|
||||
},
|
||||
'required': ['file']
|
||||
}
|
||||
},
|
||||
responses={
|
||||
200: OpenApiExample('成功', value={'url': 'http://.../media/uploads/xxx.jpg'})
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@parser_classes([MultiPartParser, FormParser])
|
||||
def upload_image(request):
|
||||
file_obj = request.FILES.get('file')
|
||||
if not file_obj:
|
||||
return Response({'error': 'No file uploaded'}, status=400)
|
||||
|
||||
# 验证文件类型
|
||||
if not file_obj.content_type.startswith('image/'):
|
||||
return Response({'error': 'File is not an image'}, status=400)
|
||||
|
||||
# 生成唯一文件名
|
||||
ext = os.path.splitext(file_obj.name)[1]
|
||||
filename = f"uploads/avatars/{uuid.uuid4()}{ext}"
|
||||
|
||||
# 保存文件
|
||||
path = default_storage.save(filename, ContentFile(file_obj.read()))
|
||||
|
||||
# 获取完整URL
|
||||
# 注意:如果使用了云存储,url会自动包含域名;如果是本地存储,可能需要手动拼接
|
||||
file_url = default_storage.url(path)
|
||||
|
||||
# 确保 URL 是完整的 (如果是相对路径,拼接当前 host)
|
||||
if not file_url.startswith('http'):
|
||||
file_url = request.build_absolute_uri(file_url)
|
||||
|
||||
return Response({'url': file_url})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user