Files

437 lines
16 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.
"""把现站公开摘录写入首页各模块,供本地查看。
数据来自仓库 docs/参考/现站/基础数据。重复执行会清掉首页模块后按同一批摘录重写,
不改站点备案号,也不改当前启用的模板。口令只从环境变量读取。
"""
from __future__ import annotations
import json
import tempfile
from html import escape, unescape
from pathlib import Path
from sqlalchemy import delete, select
from app.db import Base, SessionLocal, get_engine
from app.models.content import AdminFile, SiteSetting, ensure_site_defaults, save_settings
from app.models.home import (
BRAND,
TECH,
HomeArticle,
HomeArticleAttachment,
HomeAudienceLink,
HomeComplianceLink,
HomeContact,
HomeHonor,
HomeHotLink,
HomeMarquee,
HomeMember,
HomeMenuItem,
HomeRelatedLink,
HomeSlide,
HomeSlideAttachment,
HomeTemplate,
create_article,
create_slide,
ensure_home_templates,
save_audiences,
save_compliance,
save_contact,
save_honors,
save_hot_links,
save_members,
save_menu,
save_related_links,
set_article_display,
)
from app.services.storage import ensure_buckets, make_object_key, public_object_url, upload_object
ROOT = Path(__file__).resolve().parents[3]
SOURCE = ROOT / "docs" / "参考" / "现站" / "基础数据"
CAROUSEL = SOURCE / "首页轮播"
NEWS = SOURCE / "新闻动态"
NOTICES = SOURCE / "通知公告"
POLICIES = SOURCE / "政策法规"
SLOGAN = "服务政府、服务行业、服务会员"
VALUE = "独立、客观、公正执业"
# 现站轮播图对应的新闻编号,见首页解码 HTML。
SLIDES = [
("58612080.jpg", 8560786),
("52398194.png", 7519867),
("50933127.jpg", 7282421),
("49922210.jpg", 7108127),
("45355362.png", 6272136),
]
# 有正文、且不与前 6 条新闻重复的市场类转载,用作数据看板文章。
DASHBOARD_IDS = (7544540, 7519901, 6888657)
# 张掖路 63 号所在城关区的大致坐标,只用于地图测试。
LATITUDE = 36.0609
LONGITUDE = 103.8234
def _load_index(folder: Path) -> list[dict]:
return json.loads((folder / "index.json").read_text(encoding="utf-8"))
def _article_dir(folder: Path, article_id: int) -> Path:
return folder / "文章" / str(article_id)
def _paragraphs(path: Path) -> str:
text = unescape(path.read_text(encoding="utf-8"))
parts = []
for line in text.splitlines():
line = line.strip()
if not line:
continue
parts.append(f"<p>{escape(line)}</p>")
return "".join(parts)
def _content_type(path: Path) -> str:
suffix = path.suffix.lower()
if suffix == ".png":
return "image/png"
if suffix == ".webp":
return "image/webp"
if suffix == ".gif":
return "image/gif"
return "image/jpeg"
def _store_public(db, path: Path, filename: str | None = None) -> AdminFile:
name = filename or f"home-sample-{path.name}"
row = db.scalar(
select(AdminFile).where(AdminFile.bucket == "public", AdminFile.filename == name)
)
if row is not None:
return row
data = path.read_bytes()
key = make_object_key(name)
upload_object("public", key, data, _content_type(path))
row = AdminFile(
bucket="public",
object_key=key,
filename=name,
content_type=_content_type(path),
size=len(data),
created_by=None,
)
db.add(row)
db.commit()
db.refresh(row)
return row
def _nameplate(name: str, dest: Path) -> None:
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (360, 120), (15, 76, 129))
draw = ImageDraw.Draw(image)
font_path = "/System/Library/Fonts/Hiragino Sans GB.ttc"
font = ImageFont.truetype(font_path, 22)
# 两行排开,避免会员名称超出标志宽度。
midpoint = max(8, len(name) // 2)
split = name.rfind("有限", 0, midpoint + 6)
if split < 4:
split = midpoint
lines = [name[:split], name[split:]]
draw.multiline_text((18, 28), "\n".join(lines), fill=(255, 255, 255), font=font, spacing=6)
dest.parent.mkdir(parents=True, exist_ok=True)
image.save(dest, "PNG")
def _clear_home(db) -> None:
for model in (
HomeSlideAttachment,
HomeSlide,
HomeArticleAttachment,
HomeArticle,
HomeAudienceLink,
HomeHotLink,
HomeHonor,
HomeMember,
HomeRelatedLink,
HomeComplianceLink,
HomeContact,
HomeMarquee,
):
db.execute(delete(model))
db.execute(delete(HomeMenuItem))
db.commit()
def _import_articles(
db, folder: Path, category: str, limit: int, skip: set[int], only: set[int] | None = None
) -> dict[int, int]:
created: dict[int, int] = {}
count = 0
for item in _load_index(folder):
article_id = int(item["id"])
if only is not None and article_id not in only:
continue
if article_id in skip or not item.get("bodySaved"):
continue
body_path = _article_dir(folder, article_id) / "content.txt"
if not body_path.is_file():
continue
image_id = None
meta_path = _article_dir(folder, article_id) / "meta.json"
if meta_path.is_file():
meta = json.loads(meta_path.read_text(encoding="utf-8"))
images = meta.get("images") or []
if images:
picture = _article_dir(folder, article_id) / images[0]["file"]
if picture.is_file():
image_id = _store_public(db, picture).id
html = _paragraphs(body_path)
html += f"<p>整理自现站公开页面:{escape(item['url'])}</p>"
created[article_id] = create_article(
db,
{
"category": category,
"title": item["title"][:200],
"bodyHtml": html,
"imageFileId": image_id,
"published": True,
"publishedAt": f"{item['date']}T08:00:00",
"attachmentFileIds": [],
},
)
count += 1
if count >= limit:
break
return created
def _related(name: str) -> list[dict]:
groups = {
"主管部门": [
("甘肃省住房和城乡建设厅", "http://www.gsjs.gansu.gov.cn/?tabid=1"),
("中华人民共和国住房和城乡建设部", "https://www.mohurd.gov.cn/"),
],
"相关部门": [
("甘肃省人力资源和社会保障厅", "http://www.rst.gansu.gov.cn/"),
("国家税务总局甘肃省税务局", "http://www.gstax.gov.cn/"),
],
"合作协会": [
("中国房地产估价师与房地产经纪人学会", "http://www.cirea.org.cn/"),
("英国皇家特许测量师学会(RICS)", "https://www.rics.org/cn/"),
("香港测量师学会(HKIS)", "https://www.hkis.org.hk/en/index.html"),
],
}
return [
{"label": label, "url": url, "visible": True, "sortOrder": index}
for index, (label, url) in enumerate(groups[name])
]
def seed() -> None:
if not SOURCE.is_dir():
raise SystemExit(f"找不到现站摘录:{SOURCE}")
engine = get_engine()
Base.metadata.create_all(bind=engine)
SessionLocal.configure(bind=engine)
db = SessionLocal()
try:
ensure_buckets()
ensure_site_defaults(db)
ensure_home_templates(db)
news_index = {int(item["id"]): item for item in _load_index(NEWS)}
logo = SOURCE / "logo" / "logo.jpg"
if logo.is_file():
logo_row = _store_public(db, logo, "home-sample-logo.jpg")
current = db.scalar(select(SiteSetting).where(SiteSetting.key == "logo_file_id"))
if current is None or not str(current.value).strip():
save_settings(db, {"logo_file_id": str(logo_row.id)})
_clear_home(db)
active = db.scalar(select(HomeTemplate.name).where(HomeTemplate.is_active.is_(True))) or TECH
for row in db.scalars(select(HomeTemplate)).all():
row.slogan = SLOGAN
row.value_proposition = VALUE
row.is_active = row.name == active
db.commit()
for index, (filename, source_id) in enumerate(SLIDES):
picture = CAROUSEL / filename
meta = news_index.get(source_id, {})
title = meta.get("title") or "协会活动"
body_path = _article_dir(NEWS, source_id) / "content.txt"
html = _paragraphs(body_path) if body_path.is_file() else f"<p>{escape(title)}</p>"
if meta.get("url"):
html += f"<p>整理自现站公开页面:{escape(meta['url'])}</p>"
image = _store_public(db, picture)
create_slide(
db,
{
"title": title[:200],
"imageFileId": image.id,
"sortOrder": index,
"enabled": True,
"intervalSeconds": 6,
"published": True,
"bodyHtml": html,
"attachmentFileIds": [],
},
)
set_article_display(db, {"news": 6, "notices": 6, "policies": 6, "dashboard": 6})
_import_articles(db, NEWS, "news", 6, set(DASHBOARD_IDS))
_import_articles(db, NOTICES, "notices", 6, set())
policy_ids = _import_articles(db, POLICIES, "policies", 6, set())
policy_ids.update(_import_articles(db, POLICIES, "policies", 1, set(), only={1209303}))
dashboard_ids = _import_articles(db, NEWS, "dashboard", 3, set(), only=set(DASHBOARD_IDS))
privacy_id = create_article(
db,
{
"category": "policies",
"title": "隐私政策(测试占位)",
"bodyHtml": (
"<p>现站没有单独发布隐私政策。本条只用于首页合规链接测试,正式文本以协会后续发布为准。</p>"
"<p>协会公开介绍写明,协会遵守法律、法规和国家政策。</p>"
),
"published": True,
"publishedAt": "2019-01-01T08:00:00",
"attachmentFileIds": [],
},
)
charter = db.scalar(select(HomeArticle).where(HomeArticle.title.contains("章程")))
covenant = db.scalar(select(HomeArticle).where(HomeArticle.title.contains("自律公约")))
compliance = [
{"label": "隐私政策", "url": f"/articles/{privacy_id}", "visible": True, "sortOrder": 0},
{
"label": "使用条款",
"url": f"/articles/{covenant.id}" if covenant else f"/articles/{privacy_id}",
"visible": True,
"sortOrder": 1,
},
{
"label": "网站声明",
"url": f"/articles/{charter.id}" if charter else f"/articles/{privacy_id}",
"visible": True,
"sortOrder": 2,
},
]
save_audiences(
db,
[
{
"name": "估价师",
"links": [
{"label": "估价师查询", "url": "http://xhzhglxt.cirea.org.cn/website/gjs_Iframe.asp", "visible": True, "sortOrder": 0},
{"label": "注册系统", "url": "http://gjszcxt.cirea.org.cn/", "visible": True, "sortOrder": 1},
{"label": "信用档案", "url": "http://gjxydaxt.cirea.org.cn/", "visible": True, "sortOrder": 2},
],
},
{
"name": "估价机构",
"links": [
{"label": "估价机构查询", "url": "http://xhzhglxt.cirea.org.cn/website/gjjg_iframe.asp", "visible": True, "sortOrder": 0},
{"label": "备案系统", "url": "http://gjjgzzxt.cirea.org.cn/", "visible": True, "sortOrder": 1},
],
},
{
"name": "公众",
"links": [
{"label": "联系我们", "url": "/contact", "visible": True, "sortOrder": 0},
{"label": "机构名录", "url": "/institutions", "visible": True, "sortOrder": 1},
{"label": "投诉举报", "url": "/discipline", "visible": True, "sortOrder": 2},
],
},
],
)
save_hot_links(
db,
[
{"key": "reportQuery", "label": "报告查询", "url": "/reports/query", "visible": True, "sortOrder": 0},
{"key": "reportReview", "label": "报告审核", "url": "/reports/review", "visible": True, "sortOrder": 1},
{"key": "complaint", "label": "投诉举报", "url": "/discipline", "visible": True, "sortOrder": 2},
],
)
save_menu(
db,
[
{"key": "member", "label": "会员服务", "url": "http://www.gsrea.org.cn/hyfw", "visible": True, "sortOrder": 1},
],
)
honor_dir = _article_dir(NEWS, 7911765)
honor_meta = json.loads((honor_dir / "meta.json").read_text(encoding="utf-8"))
honor_items = []
for index, image in enumerate(honor_meta.get("images") or []):
picture = honor_dir / image["file"]
if not picture.is_file():
continue
honor_items.append({"imageFileId": _store_public(db, picture).id, "sortOrder": index})
if len(honor_items) >= 4:
break
save_honors(db, 8, honor_items)
# 现站首页滚动的单位,网址来自首页外链;没有网址的保持空链接。
members = [
("甘肃中瑞房地产资产评估有限公司", "http://www.lzzr.org.cn"),
("甘肃信诺房地产土地资产评估有限公司", ""),
("兰州三信房地产评估有限公司", ""),
("甘肃华澳房地产估价有限公司", ""),
("甘肃宏信房地资产评估有限公司", ""),
("天水中恒房地产土地资产评估咨询有限公司", ""),
("兰州中信房地产土地评估事务有限公司", "http://www.8479514.com"),
("甘肃方家房地产评估有限公司", "http://www.gstdpg.com"),
("甘肃智信房地产咨询评估有限公司", "http://www.gansuzhixin.com"),
]
member_items = []
plate_dir = Path(tempfile.gettempdir()) / "gsrea-member-plates"
for index, (name, url) in enumerate(members):
plate = plate_dir / f"{index}.png"
_nameplate(name, plate)
member_items.append(
{
"name": name,
"url": url,
"imageFileId": _store_public(db, plate, f"home-sample-member-{index}.png").id,
"sortOrder": index,
}
)
save_members(db, 8, member_items)
contact = {
"address": "甘肃省兰州市城关区张掖路63号",
"phone": "0931-8126195",
"email": "gsrea2018@163.com",
"latitude": LATITUDE,
"longitude": LONGITUDE,
}
save_contact(db, [{"name": TECH, **contact}, {"name": BRAND, **contact}])
for template in (TECH, BRAND):
save_related_links(
db,
template,
[
{"name": "主管部门", "links": _related("主管部门")},
{"name": "相关部门", "links": _related("相关部门")},
{"name": "合作协会", "links": _related("合作协会")},
],
)
save_compliance(db, template, compliance)
print(
"首页测试数据已写入:"
f"轮播 {len(SLIDES)},新闻 6,通知 6,政策 {len(policy_ids)},"
f"数据看板 {len(dashboard_ids)},荣誉 {len(honor_items)},会员 {len(member_items)}"
)
finally:
db.close()
if __name__ == "__main__":
seed()