chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests) - Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR imports keep post/product types instead of collapsing to page - Docs: trade-pro theme port spec (R3) and implementation plan Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""Content assembly: stable, site-scoped brand/category relationships.
|
||||
|
||||
Only materializes associations supported by source data. Generated brand shells
|
||||
are clearly marked and contain no fabricated copy.
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from . import db
|
||||
from .core import save_entry
|
||||
from .theme_pro import fields,slug
|
||||
|
||||
KINDS=('product','vehicle')
|
||||
|
||||
def preview(site_id):
|
||||
records=db.rows("SELECT * FROM entries WHERE site_id=? AND type IN ('product','vehicle','brand')",(site_id,))
|
||||
brand_pages={r['title'].casefold():r for r in records if r['type']=='brand'}
|
||||
routes={r['route'] for r in db.rows('SELECT route FROM entries WHERE site_id=?',(site_id,))}
|
||||
brands=defaultdict(list);cats=defaultdict(list);issues=[]
|
||||
for row in records:
|
||||
if row['type'] not in KINDS:continue
|
||||
attrs=fields(row)
|
||||
brand=str(attrs.get('brand') or '').strip();category=str(attrs.get('category') or '').strip()
|
||||
if brand: brands[brand].append(row['route'])
|
||||
else:issues.append({'route':row['route'],'severity':'review','message':'未填写品牌'})
|
||||
if category:cats[category].append(row['route'])
|
||||
else:issues.append({'route':row['route'],'severity':'review','message':'未填写分类'})
|
||||
if not row['cover']:issues.append({'route':row['route'],'severity':'review','message':'缺少产品封面图'})
|
||||
candidates=[]
|
||||
for name,products in brands.items():
|
||||
if name.casefold() in brand_pages:continue
|
||||
url=f'/brands/{slug(name)}/'
|
||||
if url in routes:
|
||||
issues.append({'route':url,'severity':'block','message':'品牌自动生成路径与已有页面冲突'});continue
|
||||
candidates.append({'brand':name,'route':url,'products':len(products)})
|
||||
return {'brands':[{ 'name':k,'items':len(v)} for k,v in brands.items()],
|
||||
'categories':[{'name':k,'items':len(v)} for k,v in cats.items()],
|
||||
'brand_pages_missing':candidates,'issues':issues,
|
||||
'products':sum(len(x) for x in brands.values()),
|
||||
'note':'仅根据已有产品字段建立关系;缺少真实品牌介绍/图片时不会自动编造。'}
|
||||
|
||||
def commit(site_id):
|
||||
result=preview(site_id)
|
||||
if any(issue['severity']=='block' for issue in result['issues']):
|
||||
raise ValueError('存在路径冲突,请先处理后再装配')
|
||||
created=[]
|
||||
for brand in result['brand_pages_missing']:
|
||||
save_entry(site_id,{'type':'brand','title':brand['brand'],'route':brand['route'],
|
||||
'summary':'','body':'','status':'published','meta':{'fields':{'source':'auto-assembled'},'source_auto_assembled':True}},
|
||||
source='relations-assembly')
|
||||
created.append(brand['route'])
|
||||
return {'created_brand_pages':created,'count':len(created),'preview':result}
|
||||
Reference in New Issue
Block a user