"""把现站公开摘录写入首页各模块,供本地查看。 数据来自仓库 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"

{escape(line)}

") 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"

整理自现站公开页面:{escape(item['url'])}

" 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"

{escape(title)}

" if meta.get("url"): html += f"

整理自现站公开页面:{escape(meta['url'])}

" 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": ( "

现站没有单独发布隐私政策。本条只用于首页合规链接测试,正式文本以协会后续发布为准。

" "

协会公开介绍写明,协会遵守法律、法规和国家政策。

" ), "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()