feat: 龙先生审核目录重置为娱乐陪伴正能量商品
三审核类型改名;每类型2专区×10商品;避开代练代打;dry-run双确认。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
332
jituan/management/commands/reset_lxs_shenhe_yule_peiban.py
Normal file
332
jituan/management/commands/reset_lxs_shenhe_yule_peiban.py
Normal file
@@ -0,0 +1,332 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
龙先生(lxs)审核目录重置为「娱乐陪伴」正向内容(非代练代打、不过度游戏化)。
|
||||
|
||||
针对:全局 3 个 shenhezhuangtai=2 的商品类型 + 俱乐部 lxs 下专区/商品。
|
||||
|
||||
动作:
|
||||
1) 将 3 个审核类型名改为:娱乐陪伴 / 休闲同行 / 友好互动(改全局类型名,dry-run 会提示)
|
||||
2) 清空 lxs 下这些类型的审核专区+商品(只删 DB,不删 OSS)
|
||||
3) 每个类型建 2 个专区;每个专区 10 个商品(共 60)
|
||||
4) 图标一律 a_long/morentouxiang.jpg
|
||||
5) 文案纯正能量陪伴/交流,禁止代练代打上分等词
|
||||
|
||||
用法:
|
||||
python manage.py reset_lxs_shenhe_yule_peiban
|
||||
python manage.py reset_lxs_shenhe_yule_peiban --list
|
||||
python manage.py reset_lxs_shenhe_yule_peiban --apply --i-confirm=lxs-yule-peiban
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
ALLOWED_CLUB_ID = 'lxs'
|
||||
NAME_HINT = '龙先生'
|
||||
SHENHE = 2
|
||||
DEFAULT_AVATAR = 'a_long/morentouxiang.jpg'
|
||||
CONFIRM_TOKEN = 'lxs-yule-peiban'
|
||||
EXPECTED_TYPE_COUNT = 3
|
||||
|
||||
# 三个审核类型目标名称(按现有审核类型 id 升序一一对应)
|
||||
TYPE_NAMES = ['娱乐陪伴', '休闲同行', '友好互动']
|
||||
|
||||
# 每个类型 2 个专区名
|
||||
ZONE_NAMES_BY_TYPE = [
|
||||
['温暖陪伴', '轻松畅聊'],
|
||||
['兴趣交流', '日常鼓励'],
|
||||
['正能量同行', '休闲时光'],
|
||||
]
|
||||
|
||||
# 每个专区 10 个商品:(标题, 介绍, 价格)
|
||||
# 刻意弱化具体竞技/上分,强调陪伴、交流、正向娱乐
|
||||
PRODUCTS_PER_ZONE = [
|
||||
[
|
||||
('暖心语音陪伴', '友好语音交流,倾听分享,营造轻松正能量氛围。', '19.90'),
|
||||
('轻松心情疏导', '温和聊天陪伴,帮助放松心情,传递积极态度。', '19.90'),
|
||||
('兴趣话题畅聊', '围绕健康兴趣爱好交流,互相启发,气氛友善。', '15.90'),
|
||||
('正能量晚间陪伴', '晚间轻松聊天,分享日常点滴,友好互动。', '19.90'),
|
||||
('学习打卡互勉', '互相提醒学习计划,鼓励坚持,共同进步。', '15.90'),
|
||||
('运动打卡陪伴', '交流运动习惯与打卡心得,互相鼓励坚持。', '15.90'),
|
||||
('阅读心得分享', '分享好书与阅读感受,文明交流,开阔视野。', '12.90'),
|
||||
('音乐放松同行', '一起听歌聊天放松,分享喜爱曲目,气氛轻松。', '12.90'),
|
||||
('生活小妙招交流', '交流生活经验与小技巧,互助友爱。', '12.90'),
|
||||
('周末休闲畅聊', '周末轻松闲聊,分享愉快见闻,友好陪伴。', '15.90'),
|
||||
],
|
||||
[
|
||||
('兴趣社群式交流', '按兴趣轻松交流,结识同好,氛围文明友好。', '15.90'),
|
||||
('手工创作分享', '分享手工与创作过程,互相欣赏鼓励。', '15.90'),
|
||||
('摄影见闻交流', '交流摄影与风景见闻,传递美好感受。', '12.90'),
|
||||
('厨艺心得闲聊', '聊聊家常菜与烹饪心得,轻松愉快。', '12.90'),
|
||||
('宠物日常分享', '分享萌宠日常,友好互动,温暖治愈。', '12.90'),
|
||||
('旅行见闻讲述', '讲述旅行见闻与风景,开阔眼界,正能量满满。', '19.90'),
|
||||
('电影观后闲谈', '交流观影感受与推荐,文明讨论,轻松愉快。', '12.90'),
|
||||
('桌游规则解说', '友好讲解休闲桌游玩法,一起快乐体验。', '15.90'),
|
||||
('棋类休闲对弈', '休闲棋类友好对弈,重在交流与乐趣。', '15.90'),
|
||||
('午后茶歇闲聊', '午后轻松聊天陪伴,舒缓节奏,气氛温馨。', '12.90'),
|
||||
],
|
||||
[
|
||||
('早安鼓励打卡', '清晨互相问候鼓励,开启积极一天。', '9.90'),
|
||||
('习惯养成互勉', '一起制定小目标,互相督促,温柔坚持。', '15.90'),
|
||||
('情绪友好倾听', '认真倾听烦恼,给予友善支持与正向建议。', '19.90'),
|
||||
('时间管理小谈', '交流高效作息小方法,互相提醒,积极向上。', '15.90'),
|
||||
('环保生活分享', '分享绿色生活小习惯,传递责任与温暖。', '9.90'),
|
||||
('志愿服务见闻', '交流公益与志愿服务见闻,弘扬正能量。', '12.90'),
|
||||
('亲子沟通小贴士', '交流亲子相处经验,友善互助(非医疗建议)。', '15.90'),
|
||||
('职场礼仪闲谈', '聊聊职场沟通礼仪与礼貌习惯,积极文明。', '15.90'),
|
||||
('节日祝福陪伴', '节日期间友好问候与陪伴聊天,传递祝福。', '12.90'),
|
||||
('睡前放松畅聊', '睡前轻松聊天,帮助放松,友好结束一天。', '15.90'),
|
||||
],
|
||||
]
|
||||
|
||||
BANNED_WORDS = (
|
||||
'代练', '代打', '上分', '上段', '包星', '定位赛', '代肝',
|
||||
'代刷', '代打手', '代练平台', '代练服务', '护航上分',
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '龙先生审核类型改为娱乐陪伴并向 lxs 写入正能量专区/商品(默认 dry-run)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--apply', action='store_true', help='真正写库')
|
||||
parser.add_argument(
|
||||
'--i-confirm',
|
||||
type=str,
|
||||
default='',
|
||||
help=f'必须等于 {CONFIRM_TOKEN} 才允许 --apply',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--list',
|
||||
action='store_true',
|
||||
help='只列出当前审核类型及 lxs 存量',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-rename-types',
|
||||
action='store_true',
|
||||
help='不修改全局审核类型名称,仅重建 lxs 专区/商品',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from jituan.models import Club
|
||||
from products.models import Shangpin, ShangpinLeixing, ShangpinZhuanqu
|
||||
|
||||
apply = bool(options.get('apply'))
|
||||
confirm = (options.get('i_confirm') or '').strip()
|
||||
skip_rename = bool(options.get('skip_rename_types'))
|
||||
|
||||
club = Club.query.filter(club_id=ALLOWED_CLUB_ID).first()
|
||||
if not club:
|
||||
raise CommandError(f'俱乐部 {ALLOWED_CLUB_ID} 不存在')
|
||||
if NAME_HINT not in (club.name or ''):
|
||||
raise CommandError(
|
||||
f'安全拦截:{ALLOWED_CLUB_ID} name={club.name!r} 不含「{NAME_HINT}」'
|
||||
)
|
||||
|
||||
audit_types = list(
|
||||
ShangpinLeixing.query.filter(shenhezhuangtai=SHENHE).order_by('id')
|
||||
)
|
||||
if len(audit_types) != EXPECTED_TYPE_COUNT:
|
||||
raise CommandError(
|
||||
f'安全拦截:审核类型数量应为 {EXPECTED_TYPE_COUNT},'
|
||||
f'当前为 {len(audit_types)}:'
|
||||
+ ', '.join(f'id={t.id}「{t.jieshao}」' for t in audit_types)
|
||||
)
|
||||
|
||||
leixing_ids = [t.id for t in audit_types]
|
||||
|
||||
def counts():
|
||||
z = ShangpinZhuanqu.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
leixing_id__in=leixing_ids,
|
||||
shenhezhuangtai=SHENHE,
|
||||
dianpu__isnull=True,
|
||||
).count()
|
||||
p = Shangpin.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
leixing_id__in=leixing_ids,
|
||||
shenhezhuangtai=SHENHE,
|
||||
dianpu__isnull=True,
|
||||
).count()
|
||||
return z, p
|
||||
|
||||
if options.get('list'):
|
||||
self.stdout.write(self.style.NOTICE(
|
||||
f'审核类型 → 计划改名;lxs 当前专区/商品:'
|
||||
))
|
||||
for i, t in enumerate(audit_types):
|
||||
z = ShangpinZhuanqu.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID, leixing_id=t.id,
|
||||
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||
).count()
|
||||
p = Shangpin.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID, leixing_id=t.id,
|
||||
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||
).count()
|
||||
self.stdout.write(
|
||||
f' id={t.id}「{t.jieshao}」→「{TYPE_NAMES[i]}」 '
|
||||
f'lxs zones={z} products={p}'
|
||||
)
|
||||
return
|
||||
|
||||
# 文案自检
|
||||
for zone_list in PRODUCTS_PER_ZONE:
|
||||
if len(zone_list) != 10:
|
||||
raise CommandError('内部错误:每个专区必须恰好 10 个商品')
|
||||
for title, intro, _price in zone_list:
|
||||
blob = title + intro
|
||||
for w in BANNED_WORDS:
|
||||
if w in blob:
|
||||
raise CommandError(f'文案含禁止词「{w}」: {title}')
|
||||
|
||||
for zones in ZONE_NAMES_BY_TYPE:
|
||||
if len(zones) != 2:
|
||||
raise CommandError('内部错误:每个类型必须恰好 2 个专区')
|
||||
|
||||
if apply and confirm != CONFIRM_TOKEN:
|
||||
raise CommandError(
|
||||
f'安全拦截:--apply 必须带 --i-confirm={CONFIRM_TOKEN}\n'
|
||||
f' python manage.py reset_lxs_shenhe_yule_peiban '
|
||||
f'--apply --i-confirm={CONFIRM_TOKEN}'
|
||||
)
|
||||
|
||||
old_z, old_p = counts()
|
||||
mode = 'APPLY' if apply else 'DRY-RUN'
|
||||
self.stdout.write(self.style.NOTICE(
|
||||
f'\n===== {mode} 龙先生审核 → 娱乐陪伴 =====\n'
|
||||
f'club={ALLOWED_CLUB_ID} ({club.name})\n'
|
||||
f'将清空 lxs 审核专区={old_z} 商品={old_p},再写入 '
|
||||
f'{EXPECTED_TYPE_COUNT}类型×2专区×10商品=60\n'
|
||||
f'图标: {DEFAULT_AVATAR}\n'
|
||||
f'改名全局类型: {"否" if skip_rename else "是(影响所有俱乐部审核展示名)"}\n'
|
||||
))
|
||||
|
||||
self.stdout.write('—— 类型改名计划 ——')
|
||||
for i, t in enumerate(audit_types):
|
||||
self.stdout.write(f' id={t.id}「{t.jieshao}」→「{TYPE_NAMES[i]}」')
|
||||
|
||||
self.stdout.write('—— 专区/商品结构 ——')
|
||||
for i, tname in enumerate(TYPE_NAMES):
|
||||
for zn in ZONE_NAMES_BY_TYPE[i]:
|
||||
self.stdout.write(f' 「{tname}」/「{zn}」→ 10 个正能量商品')
|
||||
|
||||
self.stdout.write('—— 商品样例 ——')
|
||||
for title, intro, price in PRODUCTS_PER_ZONE[0][:3]:
|
||||
self.stdout.write(f' {title} ¥{price} | {intro}')
|
||||
|
||||
if not apply:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
'\n以上仅计划。确认后:\n'
|
||||
f' python manage.py reset_lxs_shenhe_yule_peiban '
|
||||
f'--apply --i-confirm={CONFIRM_TOKEN}\n'
|
||||
))
|
||||
return
|
||||
|
||||
stats = {
|
||||
'types_renamed': 0,
|
||||
'deleted_products': 0,
|
||||
'deleted_zones': 0,
|
||||
'zones_created': 0,
|
||||
'products_created': 0,
|
||||
}
|
||||
|
||||
with transaction.atomic():
|
||||
if not skip_rename:
|
||||
for i, t in enumerate(audit_types):
|
||||
new_name = TYPE_NAMES[i]
|
||||
if (t.jieshao or '') != new_name:
|
||||
t.jieshao = new_name
|
||||
t.tupian_url = t.tupian_url or DEFAULT_AVATAR
|
||||
t.save()
|
||||
stats['types_renamed'] += 1
|
||||
|
||||
n_prod, _ = Shangpin.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
leixing_id__in=leixing_ids,
|
||||
shenhezhuangtai=SHENHE,
|
||||
dianpu__isnull=True,
|
||||
).delete()
|
||||
n_zone, _ = ShangpinZhuanqu.query.filter(
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
leixing_id__in=leixing_ids,
|
||||
shenhezhuangtai=SHENHE,
|
||||
dianpu__isnull=True,
|
||||
).delete()
|
||||
stats['deleted_products'] = n_prod
|
||||
stats['deleted_zones'] = n_zone
|
||||
|
||||
# 按类型顺序重建
|
||||
for i, lx in enumerate(audit_types):
|
||||
# 刷新名称(若已 rename)
|
||||
lx = ShangpinLeixing.query.filter(id=lx.id).first()
|
||||
zone_names = ZONE_NAMES_BY_TYPE[i]
|
||||
# 两个专区共用同一组 10 商品模板?用户要每个专区 10 个 —
|
||||
# 使用 PRODUCTS_PER_ZONE[i] 作为该类型两专区的商品池,
|
||||
# 第一专区用全部 10 个;第二专区标题加专区后缀避免同类型下标题撞车,
|
||||
# 或两专区用同一文案但标题带专区名区分。
|
||||
base_products = PRODUCTS_PER_ZONE[i]
|
||||
for zi, zname in enumerate(zone_names):
|
||||
zrow = ShangpinZhuanqu.query.create(
|
||||
mingzi=zname,
|
||||
leixing_id=lx.id,
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
tupian_url=DEFAULT_AVATAR,
|
||||
shenhezhuangtai=SHENHE,
|
||||
paixu=20 - zi * 10,
|
||||
shangjia_zhuangtai=True,
|
||||
fengjin_zhuangtai=True,
|
||||
dianpu=None,
|
||||
dianpu_leixing=None,
|
||||
)
|
||||
stats['zones_created'] += 1
|
||||
|
||||
for pi, (title, intro, price) in enumerate(base_products):
|
||||
# 同类型不同专区:标题加专区名,避免去重冲突且语义清晰
|
||||
full_title = f'{title}·{zname}'
|
||||
for w in BANNED_WORDS:
|
||||
if w in full_title or w in intro:
|
||||
raise CommandError(f'禁止词命中: {full_title}')
|
||||
Shangpin.query.create(
|
||||
biaoqian=full_title[:45],
|
||||
jiage=Decimal(price),
|
||||
kucun=99999,
|
||||
leixing_id=lx.id,
|
||||
zhuanqu_id=zrow.id,
|
||||
zhenshi_xiaoliang=0,
|
||||
duiwai_xiaoliang=0,
|
||||
jieshao=intro,
|
||||
xiadan_xuzhi=(
|
||||
'本服务为正向休闲陪伴与交流,请文明沟通。'
|
||||
'禁止任何代练、代打或不正当行为。'
|
||||
),
|
||||
guize_tupian='',
|
||||
tupian_url=DEFAULT_AVATAR,
|
||||
yaoqiuleixing=lx.yaoqiuleixing,
|
||||
huiyuan_id=lx.huiyuan_id,
|
||||
yongjin=lx.yongjin,
|
||||
kaioi_ewai_dashou_fencheng=False,
|
||||
ewai_dashou_fencheng=None,
|
||||
paixu=100 - pi,
|
||||
shenhezhuangtai=SHENHE,
|
||||
shangjia_zhuangtai=True,
|
||||
fengjin_zhuangtai=True,
|
||||
shenhe_zhuangtai=True,
|
||||
club_id=ALLOWED_CLUB_ID,
|
||||
shi_zhuanpan=False,
|
||||
dianpu=None,
|
||||
dianpu_leixing=None,
|
||||
)
|
||||
stats['products_created'] += 1
|
||||
|
||||
if stats['zones_created'] != 6 or stats['products_created'] != 60:
|
||||
raise CommandError(
|
||||
f'数量异常: zones={stats["zones_created"]} products={stats["products_created"]},'
|
||||
f'期望 6/60,已回滚'
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'\n完成: {stats}\n'
|
||||
f'均为审核专用(shenhezhuangtai=2),仅审核模式可见;文案已避开代练代打。'
|
||||
))
|
||||
Reference in New Issue
Block a user