Files
Django/jituan/views_miniapp_assets.py

241 lines
9.6 KiB
Python

"""小程序图标与频道 — 后台管理 API。"""
import os
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from backend.utils import verify_kefu_permission
from config.models import ClubMiniappIcon, ClubPindao, ClubPindaoImage
from jituan.services.club_context import resolve_effective_club_id
from jituan.services.display_config import forbid_display_write_in_all_scope, list_response_meta
from jituan.services.miniapp_assets import (
ICON_FOLDER,
MINIAPP_ICON_LABELS,
MINIAPP_ICON_META,
POSTER_FOLDER,
POSTER_ICON_KEYS,
default_icon_path,
get_miniapp_icons_dict,
get_pindao_payload,
get_poster_payload,
)
from products.views import delete_from_oss, upload_to_oss, validate_image
POPUP_PERM = '8080a'
PINDAO_FOLDER = 'beijing/pindao'
class ClubMiniappIconListView(APIView):
"""POST /jituan/houtai/miniapp-icon-list"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
if not phone:
return Response({'code': 401, 'msg': '缺少客服账号'})
kefu, permissions = verify_kefu_permission(request, phone)
if kefu is None:
return permissions
if POPUP_PERM not in permissions:
return Response({'code': 403, 'msg': '无权限管理小程序图标'})
club_id = resolve_effective_club_id(request, request.user)
icons_db = {
r.icon_key: r for r in ClubMiniappIcon.query.filter(club_id=club_id)
}
items = []
for key, label in MINIAPP_ICON_META:
row = icons_db.get(key)
items.append({
'icon_key': key,
'label': label,
'image_path': (row.image_path if row else '') or default_icon_path(key),
'description': (row.description if row else '') or '',
'is_active': row.is_active if row else True,
'is_uploaded': row.is_uploaded if row else False,
'id': row.id if row else None,
})
return Response({
'code': 0,
'data': {
'icons': items,
'folder': ICON_FOLDER,
**list_response_meta(request),
},
})
class ClubMiniappIconManageView(APIView):
"""POST /jituan/houtai/miniapp-icon-manage action=upload|save_meta"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
if not phone:
return Response({'code': 401, 'msg': '缺少客服账号'})
kefu, permissions = verify_kefu_permission(request, phone)
if kefu is None:
return permissions
if POPUP_PERM not in permissions:
return Response({'code': 403, 'msg': '无权限管理小程序图标'})
deny = forbid_display_write_in_all_scope(request)
if deny:
return deny
action = (request.data.get('action') or 'upload').strip()
club_id = resolve_effective_club_id(request, request.user)
icon_key = (request.data.get('icon_key') or '').strip()
if icon_key not in MINIAPP_ICON_LABELS:
return Response({'code': 400, 'msg': 'icon_key 无效'})
if action == 'save_meta':
description = (request.data.get('description') or '').strip()
row, _ = ClubMiniappIcon.query.get_or_create(
club_id=club_id,
icon_key=icon_key,
defaults={
'label': MINIAPP_ICON_LABELS[icon_key],
'image_path': default_icon_path(icon_key),
},
)
row.description = description
row.label = MINIAPP_ICON_LABELS[icon_key]
row.save(update_fields=['description', 'label', 'UpdateTime'])
return Response({'code': 0, 'msg': '保存成功'})
file_obj = request.FILES.get('file')
if not file_obj:
return Response({'code': 400, 'msg': '请上传图片'})
ok, msg = validate_image(file_obj)
if not ok:
return Response({'code': 400, 'msg': msg})
ext = os.path.splitext(file_obj.name)[1].lower() or '.png'
if ext not in ('.png', '.jpg', '.jpeg', '.webp'):
ext = '.png'
if icon_key in POSTER_ICON_KEYS:
oss_path = f'{POSTER_FOLDER}/{club_id}_{icon_key}{ext}'
else:
oss_path = f'{ICON_FOLDER}/{icon_key}{ext}'
row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=icon_key).first()
if row and row.image_path and row.image_path != oss_path:
delete_from_oss(row.image_path)
if not upload_to_oss(file_obj, oss_path):
return Response({'code': 500, 'msg': '图片上传失败'})
if row:
row.image_path = oss_path
row.label = MINIAPP_ICON_LABELS[icon_key]
row.is_active = True
row.is_uploaded = True
row.save(update_fields=['image_path', 'label', 'is_active', 'is_uploaded', 'UpdateTime'])
else:
ClubMiniappIcon.query.create(
club_id=club_id,
icon_key=icon_key,
label=MINIAPP_ICON_LABELS[icon_key],
image_path=oss_path,
is_active=True,
is_uploaded=True,
)
return Response({'code': 0, 'msg': '上传成功', 'data': {'image_path': oss_path}})
class ClubPindaoManageView(APIView):
"""POST /jituan/houtai/pindao-manage"""
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser, JSONParser]
def post(self, request):
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
if not phone:
return Response({'code': 401, 'msg': '缺少客服账号'})
kefu, permissions = verify_kefu_permission(request, phone)
if kefu is None:
return permissions
if POPUP_PERM not in permissions:
return Response({'code': 403, 'msg': '无权限管理频道'})
action = (request.data.get('action') or 'get').strip()
club_id = resolve_effective_club_id(request, request.user)
if action == 'get':
pindao = get_pindao_payload(club_id)
images = [
{
'id': img.id,
'image_path': img.image_path,
'sort_order': img.sort_order,
}
for img in ClubPindaoImage.query.filter(club_id=club_id).order_by('sort_order', 'id')
]
return Response({
'code': 0,
'data': {
'pindao': pindao,
'images': images,
'folder': PINDAO_FOLDER,
**list_response_meta(request),
},
})
deny = forbid_display_write_in_all_scope(request)
if deny:
return deny
if action == 'save':
channel_no = (request.data.get('channel_no') or '').strip()
title = (request.data.get('title') or '频道').strip() or '频道'
row, _ = ClubPindao.query.get_or_create(club_id=club_id, defaults={'title': title})
row.channel_no = channel_no
row.title = title
row.is_active = True
row.save(update_fields=['channel_no', 'title', 'is_active', 'UpdateTime'])
return Response({'code': 0, 'msg': '保存成功'})
if action == 'add_image':
file_obj = request.FILES.get('file')
if not file_obj:
return Response({'code': 400, 'msg': '请上传图片'})
ok, msg = validate_image(file_obj)
if not ok:
return Response({'code': 400, 'msg': msg})
ext = os.path.splitext(file_obj.name)[1].lower() or '.jpg'
import random
import string
rnd = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
oss_path = f'{PINDAO_FOLDER}/{club_id}_{rnd}{ext}'
if not upload_to_oss(file_obj, oss_path):
return Response({'code': 500, 'msg': '图片上传失败'})
pindao_row, _ = ClubPindao.query.get_or_create(club_id=club_id, defaults={'title': '频道'})
max_sort = ClubPindaoImage.query.filter(club_id=club_id).order_by('-sort_order').first()
sort_order = (max_sort.sort_order + 1) if max_sort else 0
img = ClubPindaoImage.query.create(
club_id=club_id,
pindao=pindao_row,
image_path=oss_path,
sort_order=sort_order,
)
return Response({'code': 0, 'msg': '上传成功', 'data': {'id': img.id, 'image_path': oss_path}})
if action == 'delete_image':
img_id = request.data.get('id')
if not img_id:
return Response({'code': 400, 'msg': '缺少图片 id'})
img = ClubPindaoImage.query.filter(club_id=club_id, id=img_id).first()
if not img:
return Response({'code': 404, 'msg': '图片不存在'})
if img.image_path:
delete_from_oss(img.image_path)
img.delete()
return Response({'code': 0, 'msg': '删除成功'})
return Response({'code': 400, 'msg': 'action 无效'})