aifeilong 13b1ca2b9b Namespace API routes by app and flatten package layout.
Move Django apps out of apps/ and mount them under auth/, common/, content/, and cms/ so endpoint paths map directly to the owning app.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 08:42:37 +08:00

547 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from decimal import Decimal
from django.core.management.base import BaseCommand
from cms.models import (
AboutInfo,
ContactUs,
CopyrightInfo,
ExtendLink,
HomeBanner,
Keyword,
News,
Publication,
)
from accounts.models import Member
from content.models import (
Article,
ArticleComment,
Column,
Magazine,
MagazineCategory,
NavPosition,
Organize,
OrganizeCategory,
Professor,
ReviewStatus,
RuleBand,
Specialty,
StarRule,
Subject,
WorkGroup,
WorkGroupMembership,
WorkGroupTerm,
)
def _intro_html(
disease,
domain,
population,
users,
summary,
evidence="牛津分级",
strength="仅提供分级标准无具体名称",
funding="国家自然科学基金重点项目(82030056)",
registry="PREPARE-2024CN159",
):
"""生成与线上一致的简介 HTML(<p><b>键:</b>值</p>)。"""
parts = [
("关注的疾病或主题", disease),
("领域", domain),
("目标人群", population),
("使用者", users),
("推荐意见总结", summary),
("证据质量分级", evidence),
("推荐强度分级", strength),
("资助", funding),
("注册号", registry),
]
return "".join(f"<p><b>{k}:</b>{v}</p>" for k, v in parts)
# 演示文献样本(serial_number 作为幂等键,支持 --refresh-articles)
DEMO_ARTICLES = [
{
"serial_number": "STAR-2024-001",
"title": "放射性皮肤损伤诊断和治疗指南",
"type_id": 1,
"year": 2024,
"score": "88.5",
"rank": 1,
"url": "https://www.star-guidelines.cn/example/radiation-skin",
"flags": ["STAR", "临床指南"],
"like_num": 128,
"collect_num": 56,
"share_num": 23,
"read_num": 1024,
"introduction": _intro_html(
disease="放射性皮肤损伤的诊断和治疗",
domain="诊断、治疗",
population=(
"发生放射性皮肤损伤的患者(包括肿瘤放射治疗后、核事故、放射事故、"
"长期职业暴露等情况下发生放射性皮肤损伤的患者)"
),
users="烧伤外科学、放射医学、康复医学、急救与创伤医学、循证医学等领域的医务工作者",
summary=(
"包括放射性皮肤损伤的诊断依据及分度诊断标准,治疗原则(脱离放射源、保护损伤部位、"
"消除炎症等),局部预防和治疗(糖皮质激素、非甾体类药物、外用天然制剂等的使用建议),"
"全身药物治疗(口服酶、镇痛药、抗组胺药等的使用建议),物理治疗、细胞治疗、手术治疗的适用情况,"
"以及创面清洗的方法,为放射性皮肤损伤的诊断和治疗提供依据"
),
),
"content": (
"<p>本指南围绕放射性皮肤损伤的诊断与治疗给出推荐意见,"
"涵盖分度诊断、局部与全身治疗及创面管理等内容。</p>"
),
},
{
"serial_number": "STAR-2023-002",
"title": "糖尿病诊疗共识",
"type_id": 2,
"year": 2023,
"score": "76.2",
"rank": 2,
"url": "https://www.star-guidelines.cn/example/diabetes",
"flags": ["共识", "内分泌"],
"like_num": 86,
"collect_num": 41,
"share_num": 12,
"read_num": 640,
"introduction": _intro_html(
disease="2型糖尿病的综合管理",
domain="诊断、治疗、管理",
population="成年2型糖尿病患者及高危人群",
users="内分泌科、全科、内科及相关护理人员",
summary="涵盖筛查、血糖控制目标、生活方式干预与药物治疗路径的共识推荐。",
funding="无",
registry="PREPARE-2023CN088",
),
"content": "<p>本共识总结糖尿病筛查与综合管理的关键推荐意见。</p>",
},
{
"serial_number": "STAR-2024-003",
"title": "卒中一级预防指南",
"type_id": 1,
"year": 2024,
"score": "91.0",
"rank": 3,
"url": "https://www.star-guidelines.cn/example/stroke",
"flags": ["STAR", "神经内科"],
"like_num": 210,
"collect_num": 98,
"share_num": 45,
"read_num": 1880,
"introduction": _intro_html(
disease="卒中一级预防",
domain="预防、筛查",
population="具有卒中危险因素的成年人群",
users="神经内科、心内科、全科医务工作者",
summary="针对血压、血脂、抗栓与生活方式等一级预防措施给出分级推荐。",
funding="国家重点研发计划",
registry="PREPARE-2024CN201",
),
"content": "<p>本指南聚焦卒中一级预防的危险因素干预与人群筛查策略。</p>",
},
{
"serial_number": "STAR-2022-004",
"title": "抗菌药物合理使用共识",
"type_id": 2,
"year": 2022,
"score": "69.4",
"rank": 4,
"url": "https://www.star-guidelines.cn/example/antibiotic",
"flags": ["共识", "感染"],
"like_num": 54,
"collect_num": 22,
"share_num": 9,
"read_num": 390,
"introduction": _intro_html(
disease="抗菌药物合理使用与耐药防控",
domain="治疗、管理",
population="需要抗菌药物治疗的住院与门诊患者",
users="感染科、药学、检验与临床各专科医师",
summary="明确经验性用药、降阶梯策略与病原学送检等关键共识意见。",
evidence="GRADE",
funding="无",
registry="PREPARE-2022CN045",
),
"content": "<p>本共识强调抗菌药物合理使用与耐药监测的协同管理。</p>",
},
{
"serial_number": "STAR-2023-005",
"title": "心力衰竭诊疗指南",
"type_id": 1,
"year": 2023,
"score": "84.1",
"rank": 5,
"url": "https://www.star-guidelines.cn/example/heart-failure",
"flags": ["STAR", "心血管"],
"like_num": 167,
"collect_num": 73,
"share_num": 31,
"read_num": 1210,
"introduction": _intro_html(
disease="慢性心力衰竭的诊断与治疗",
domain="诊断、治疗",
population="慢性心力衰竭患者(含射血分数降低与保留)",
users="心血管内科、老年医学、全科医务工作者",
summary="覆盖诊断评估、药物四联疗法、器械治疗与随访管理的推荐意见。",
funding="国家自然科学基金",
registry="PREPARE-2023CN156",
),
"content": "<p>本指南系统阐述慢性心力衰竭的评估流程与治疗策略。</p>",
},
]
class Command(BaseCommand):
help = "灌入国际站演示数据;可用 --refresh-articles 刷新文献演示字段"
def add_arguments(self, parser):
parser.add_argument(
"--refresh-articles",
action="store_true",
help="按 serial_number 更新/创建演示文献(含结构化简介、原文链接、标签、互动数、点评)",
)
def handle(self, *args, **options):
refresh_only = options["refresh_articles"]
if refresh_only:
self._refresh_articles()
return
if Article.objects.exists():
self.stdout.write(
self.style.WARNING(
"已有文献数据,跳过全量灌数。如需刷新文献演示数据,请执行:"
" python manage.py seed_demo --refresh-articles"
)
)
return
subject = Subject.objects.create(name="放射医学", sort=1)
Subject.objects.create(name="心血管病学", sort=2)
Subject.objects.create(name="内分泌学", sort=3)
org_cat = OrganizeCategory.objects.create(name="学术学会", sort=1)
mag_cat = MagazineCategory.objects.create(name="医学期刊", sort=1)
# 完整导航树由 sync_nav_columns 写入;此处仅在空库时调用一次
from django.core.management import call_command
call_command("sync_nav_columns")
org = Organize.objects.create(
name="STAR International Network",
description="International guideline evaluation network.",
category=org_cat,
rank=1,
flags=["STAR"],
)
mag = Magazine.objects.create(name="Global Health Review", category=mag_cat)
mag.subjects.add(subject)
committee = WorkGroup.objects.create(name="评级工作委员会", sort=1)
clinical = WorkGroup.objects.create(
name="临床评级组", parent=committee, sort=1
)
method = WorkGroup.objects.create(
name="方法学评级组", parent=committee, sort=2
)
root_term = WorkGroupTerm.objects.create(
workgroup=committee,
name="第二届",
start_year=2023,
end_year=2025,
is_current=True,
sort=1,
)
clinical_term = WorkGroupTerm.objects.create(
workgroup=clinical,
parent=root_term,
name="第二届 · 临床评级组",
start_year=2023,
end_year=2025,
is_current=True,
sort=1,
)
method_term = WorkGroupTerm.objects.create(
workgroup=method,
parent=root_term,
name="第二届 · 方法学评级组",
start_year=2023,
end_year=2025,
is_current=True,
sort=2,
)
professor = Professor.objects.create(
name="Jane Doe",
role="Chair",
job="Professor",
description="Expert in guideline methodology.",
position=1,
)
WorkGroupMembership.objects.create(
professor=professor, term=clinical_term, role="组长"
)
WorkGroupMembership.objects.create(
professor=professor, term=method_term, role="委员"
)
self._ensure_star_rules()
self._upsert_demo_articles(org=org, mag=mag, subject=subject)
News.objects.create(
title="STAR International site launches",
content="<p>Welcome to the STAR International portal.</p>",
publication_time="2024-01-15",
rank=1,
)
Publication.objects.create(
title="STAR Methodology Paper",
content="<p>Methodology overview.</p>",
url="https://example.com/paper",
publication_time="2024-03-01",
rank=1,
)
AboutInfo.objects.create(
introduction="STAR International evaluates clinical practice guidelines worldwide.",
growing_path=(
"2021年,中华医学会杂志社指南与标准研究中心联合世界卫生组织指南实施与知识转化合作中心,"
"针对现有临床实践指南评价方法和工具:条目覆盖面不足、验证不充分、维度单一和评价工作量大等问题,"
"组建了指南科学性(Scientificity)、透明性(Transparency)和适用性(Applicability)评级(Rankings)工作组,"
"研发了全面的“STAR”工具。为进一步帮助医务人员遴选和应用高质量指南,工作组以2021年中国学者牵头"
"在同行评审期刊发表的指南和共识的STAR评级为起点,持续开展大范围的系统性评级工作。"
),
work_group=[
{
"name": "研发工作组",
"content": "全国五大区域,49位临床医学、方法学、管理学、期刊编辑学等多学科专家组成",
"url": "/assets/img/about/yanfa.png",
},
{
"name": "验证工作组",
"content": "34名评级专家基于指南样本对STAR工具易用性、内在信度、评级员信度、内容效度及效标效度进行验证",
"url": "/assets/img/about/yanzheng.png",
},
{
"name": "评级工作组",
"content": "13个省级行政区、42个专业、106名专家;高级职称48名,中级职称6名,初级职称14名;项目下设指导委员会、秘书组、临床评级组、方法学评级组、质量控制组",
"url": "/assets/img/about/pingji.png",
},
],
rating_method=(
'<p style="text-align:center">'
'<img src="/assets/img/about/rating-steps.png" alt="STAR评级步骤" style="max-width:100%;height:auto" />'
"</p>"
),
)
CopyrightInfo.objects.create(
copyright="© STAR International",
responsibility="Content provided for research use.",
email="info@star-international.example",
)
ContactUs.objects.create(
serve_desc="General inquiry",
serve_email="info@star-international.example",
star_desc="STAR methodology",
star_email="star@star-international.example",
)
for i, (title, image, link) in enumerate(
[
("Banner 1", "/assets/img/banner1@2x.jpg", "/guidelineAndConsensus"),
("Banner 2", "/assets/img/banner2@2x.jpg", "/guideRanking"),
("Banner 3", "/assets/img/banner3@2x.jpg", "/about"),
],
start=1,
):
HomeBanner.objects.create(
title=title,
image_url=image,
link_url=link,
sort=i,
)
ExtendLink.objects.create(
name="STAR Guidelines CN",
link_url="https://www.star-guidelines.cn",
sort=1,
)
Keyword.objects.create(name="hypertension", search_hot=10)
Keyword.objects.create(name="diabetes", search_hot=8)
Specialty.objects.create(name="Cardiology", position=1)
Specialty.objects.create(name="Endocrinology", position=2)
self._ensure_demo_comments()
self.stdout.write(self.style.SUCCESS("演示数据灌入完成"))
def _refresh_articles(self):
"""在已有库上刷新演示文献字段,便于本地联调详情页。"""
self._ensure_star_rules()
subject, _ = Subject.objects.get_or_create(
name="放射医学", defaults={"sort": 1}
)
Subject.objects.get_or_create(name="心血管病学", defaults={"sort": 2})
Subject.objects.get_or_create(name="内分泌学", defaults={"sort": 3})
org_cat, _ = OrganizeCategory.objects.get_or_create(
name="学术学会", defaults={"sort": 1}
)
mag_cat, _ = MagazineCategory.objects.get_or_create(
name="医学期刊", defaults={"sort": 1}
)
org, _ = Organize.objects.get_or_create(
name="STAR International Network",
defaults={
"description": "International guideline evaluation network.",
"category": org_cat,
"rank": 1,
"flags": ["STAR"],
},
)
mag, _ = Magazine.objects.get_or_create(
name="Global Health Review",
defaults={"category": mag_cat},
)
mag.subjects.add(subject)
created, updated = self._upsert_demo_articles(org=org, mag=mag, subject=subject)
self._ensure_demo_comments()
self.stdout.write(
self.style.SUCCESS(
f"文献演示数据已刷新:新建 {created},更新 {updated}"
)
)
def _ensure_star_rules(self):
rule, created = StarRule.objects.get_or_create(
name="STAR 2024",
year=2024,
defaults={"is_disabled": False},
)
if created or not rule.bands.exists():
RuleBand.objects.filter(rule=rule).delete()
RuleBand.objects.create(
rule=rule,
star_low=Decimal("0"),
star_high=Decimal("30"),
star=Decimal("1"),
)
RuleBand.objects.create(
rule=rule,
star_low=Decimal("30"),
star_high=Decimal("60"),
star=Decimal("2"),
)
RuleBand.objects.create(
rule=rule,
star_low=Decimal("60"),
star_high=Decimal("100"),
star=Decimal("3"),
)
def _upsert_demo_articles(self, *, org, mag, subject):
created = 0
updated = 0
for sample in DEMO_ARTICLES:
serial = sample["serial_number"]
defaults = {
"title": sample["title"],
"type_id": sample["type_id"],
"year": sample["year"],
"score": Decimal(sample["score"]),
"rank": sample["rank"],
"url": sample["url"],
"flags": sample["flags"],
"introduction": sample["introduction"],
"content": sample["content"],
"push_date": f"{sample['year']}-06-01",
"like_num": sample["like_num"],
"collect_num": sample["collect_num"],
"share_num": sample["share_num"],
"read_num": sample["read_num"],
"is_disabled": False,
}
art, was_created = Article.objects.update_or_create(
serial_number=serial,
defaults=defaults,
)
art.organizes.set([org])
art.magazines.set([mag])
art.subjects.set([subject])
if was_created:
created += 1
else:
updated += 1
return created, updated
def _ensure_demo_comments(self):
from accounts.serializers import MemberSerializer
# 国际格式;同时兼容旧的 11 位国内号演示数据
member = (
Member.objects.filter(phone="+8613800000000").first()
or Member.objects.filter(phone="13800000000").first()
)
if member is None:
member = MemberSerializer().create(
{
"name": "Demo User",
"phone": "+8613800000000",
"organize": "Demo Hospital",
"is_vip": True,
"is_disabled": False,
"password": "demo1234",
}
)
else:
# 补齐国际格式与登录账号
MemberSerializer().update(
member,
{
"phone": "+8613800000000",
"password": "demo1234",
},
)
# 优先挂到放射性皮肤损伤指南,保证详情页可见已通过点评
article = Article.objects.filter(serial_number="STAR-2024-001").first()
if not article:
article = Article.objects.order_by("id").first()
if not article:
return
comments = [
{
"star": Decimal("4.5"),
"content": "结构清晰,分度诊断与局部治疗建议可直接用于临床参考。",
"review_status": ReviewStatus.APPROVED,
},
{
"star": Decimal("4.0"),
"content": "推荐意见总结较完整,希望补充更多物理治疗适用边界。",
"review_status": ReviewStatus.APPROVED,
},
{
"star": Decimal("3.5"),
"content": "待审核示例点评,前台不应展示。",
"review_status": ReviewStatus.PENDING,
},
]
for item in comments:
exists = ArticleComment.objects.filter(
article=article,
member=member,
content=item["content"],
).exists()
if exists:
continue
ArticleComment.objects.create(
article=article,
member=member,
star=item["star"],
content=item["content"],
review_status=item["review_status"],
)