新增 _DefaultReadme:由包名、说明、安装命令、依赖表与版本历史合成 README;仅 PackageByNameView(官网数据源)使用,配置页读到的仍是作者原文,避免合成内容被回写。
236 lines
8.9 KiB
Python
236 lines
8.9 KiB
Python
"""模块:Venom 包管理器 API
|
||
|
||
远端页面(由本服务托管、在 GERP 宿主内渲染)跨源调用以下接口:
|
||
GET /api/venom/packages/ 包列表
|
||
POST /api/venom/packages/ 新建包(包名全局唯一)
|
||
GET /api/venom/packages/<uuid>/ 包详情(配置页读取)
|
||
POST /api/venom/packages/<uuid>/config/ 保存包配置
|
||
GET /api/venom/packages/<uuid>/releases/ 发布历史
|
||
POST /api/venom/packages/<uuid>/releases/ 发布新版本(写历史)
|
||
GET /api/venom/project/<包名>/ 按包名取详情(官网调用:含 README 与依赖列表)
|
||
"""
|
||
|
||
import logging
|
||
|
||
from django.db.models import Count
|
||
from django.utils.dateformat import format as date_format
|
||
from rest_framework.response import Response
|
||
from rest_framework.views import APIView
|
||
|
||
from .models import VenomPackage, VenomPackageRelease
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _ok(data=None, msg='success'):
|
||
return Response({'code': 200, 'msg': msg, 'data': data})
|
||
|
||
|
||
def _err(msg, code=400, status=None):
|
||
return Response({'code': code, 'msg': msg, 'data': None}, status=status or 200)
|
||
|
||
|
||
def _Time(value):
|
||
return date_format(value, 'Y-m-d H:i:s') if value else ''
|
||
|
||
|
||
def _PackageRow(package):
|
||
return {
|
||
'PackageUUID': str(package.PackageUUID),
|
||
'PackageName': package.PackageName,
|
||
'PackageDesc': package.PackageDesc or '',
|
||
'CreatorName': package.CreatorName or '',
|
||
'CreateTime': _Time(package.CreateTime),
|
||
'UpdateTime': _Time(package.UpdateTime),
|
||
'ReleaseCount': getattr(package, 'release_count', 0),
|
||
}
|
||
|
||
|
||
def _ReleaseRow(release):
|
||
return {
|
||
'ReleaseUUID': str(release.ReleaseUUID),
|
||
'Version': release.Version,
|
||
'Notes': release.Notes or '',
|
||
'PublisherName': release.PublisherName or '',
|
||
'CreateTime': _Time(release.CreateTime),
|
||
}
|
||
|
||
|
||
def _GetPackage(PackageUUID):
|
||
return VenomPackage.objects.annotate(release_count=Count('releases')).filter(
|
||
PackageUUID=PackageUUID).first()
|
||
|
||
|
||
def _NormalizeDependencies(value):
|
||
"""依赖列表标准化为 [{'Name', 'Version'}];非法项丢弃、同名去重、限 200 条
|
||
|
||
兼容两种入参:字符串(仅包名)与对象 {'Name','Version'}(Version 可为空 = 无版本约束)。
|
||
"""
|
||
if not isinstance(value, list):
|
||
return []
|
||
rows, seen = [], set()
|
||
for item in value[:200]:
|
||
if isinstance(item, str):
|
||
name, version = item.strip(), ''
|
||
elif isinstance(item, dict):
|
||
name = str(item.get('Name') or '').strip()
|
||
version = str(item.get('Version') or '').strip()
|
||
else:
|
||
continue
|
||
if not name or name in seen:
|
||
continue
|
||
seen.add(name)
|
||
rows.append({'Name': name[:128], 'Version': version[:64]})
|
||
return rows
|
||
|
||
|
||
def _DetailRow(package):
|
||
"""包详情行(含 README/依赖/发布历史;官网与配置页共用)"""
|
||
releases = list(package.releases.all())
|
||
return {
|
||
**_PackageRow(package),
|
||
'ConfigText': package.ConfigText or '',
|
||
'Readme': package.Readme or '',
|
||
'Dependencies': package.Dependencies or [],
|
||
'LatestVersion': releases[0].Version if releases else '',
|
||
'Releases': [_ReleaseRow(r) for r in releases],
|
||
}
|
||
|
||
|
||
def _DefaultReadme(package, releases):
|
||
"""包作者未填写 README 时,由包信息合成一份 Markdown 默认 README
|
||
|
||
仅用于官网展示(`PackageByNameView`);配置页读取到的仍是「作者填写的原文」,
|
||
避免把合成内容回写成用户数据。
|
||
"""
|
||
lines = [f'# {package.PackageName}', '']
|
||
lines += [package.PackageDesc.strip() or '该软件包暂未填写说明。', '']
|
||
|
||
lines += ['## 安装', '', '```bash', f'venom install {package.PackageName}', '```', '']
|
||
|
||
dependencies = package.Dependencies or []
|
||
lines += ['## 依赖', '']
|
||
if dependencies:
|
||
lines += ['| 依赖包 | 版本约束 |', '| --- | --- |']
|
||
lines += [f"| {d.get('Name', '')} | {d.get('Version') or '*'} |" for d in dependencies]
|
||
else:
|
||
lines += ['无外部依赖。']
|
||
lines += ['']
|
||
|
||
lines += ['## 版本历史', '']
|
||
if releases:
|
||
lines += [f"- `{r.Version}` · {_Time(r.CreateTime)}" for r in releases]
|
||
else:
|
||
lines += ['尚未发布任何版本。']
|
||
lines += ['']
|
||
|
||
return '\n'.join(lines)
|
||
|
||
|
||
class PackageListView(APIView):
|
||
"""包列表 / 新建包"""
|
||
|
||
def get(self, request):
|
||
keyword = (request.query_params.get('Keyword') or '').strip()
|
||
qs = VenomPackage.objects.annotate(release_count=Count('releases'))
|
||
if keyword:
|
||
qs = qs.filter(PackageName__icontains=keyword)
|
||
rows = [_PackageRow(p) for p in qs]
|
||
return _ok({'list': rows, 'total': len(rows)})
|
||
|
||
def post(self, request):
|
||
name = (request.data.get('PackageName') or '').strip()
|
||
if not name:
|
||
return _err('请填写包名')
|
||
if len(name) > 128:
|
||
return _err('包名过长(最多 128 字)')
|
||
if VenomPackage.objects.filter(PackageName=name).exists():
|
||
return _err(f'包名「{name}」已存在(包名必须全局唯一)')
|
||
package = VenomPackage.objects.create(
|
||
PackageName=name,
|
||
PackageDesc=(request.data.get('PackageDesc') or '').strip()[:512],
|
||
ConfigText=request.data.get('ConfigText') or '',
|
||
Dependencies=_NormalizeDependencies(request.data.get('Dependencies')),
|
||
Readme=request.data.get('Readme') or '',
|
||
CreatorName=(request.data.get('CreatorName') or '').strip()[:128],
|
||
)
|
||
package = _GetPackage(package.PackageUUID)
|
||
return _ok(_DetailRow(package), msg='软件包已创建')
|
||
|
||
|
||
class PackageDetailView(APIView):
|
||
"""包详情(配置页读取;含 README / 依赖列表 / 发布历史)"""
|
||
|
||
def get(self, request, package_uuid):
|
||
package = _GetPackage(package_uuid)
|
||
if package is None:
|
||
return _err('软件包不存在', 404)
|
||
return _ok(_DetailRow(package))
|
||
|
||
|
||
class PackageConfigView(APIView):
|
||
"""保存包配置(说明 / 配置内容 / 依赖列表 / README)"""
|
||
|
||
def post(self, request, package_uuid):
|
||
package = VenomPackage.objects.filter(PackageUUID=package_uuid).first()
|
||
if package is None:
|
||
return _err('软件包不存在', 404)
|
||
if 'PackageDesc' in request.data:
|
||
package.PackageDesc = (request.data.get('PackageDesc') or '').strip()[:512]
|
||
if 'ConfigText' in request.data:
|
||
package.ConfigText = request.data.get('ConfigText') or ''
|
||
if 'Dependencies' in request.data:
|
||
package.Dependencies = _NormalizeDependencies(request.data.get('Dependencies'))
|
||
if 'Readme' in request.data:
|
||
package.Readme = request.data.get('Readme') or ''
|
||
package.save(update_fields=[
|
||
'PackageDesc', 'ConfigText', 'Dependencies', 'Readme', 'UpdateTime'])
|
||
return _ok(_DetailRow(_GetPackage(package.PackageUUID)), msg='配置已保存')
|
||
|
||
|
||
class PackageReleaseView(APIView):
|
||
"""发布历史 / 发布新版本"""
|
||
|
||
def get(self, request, package_uuid):
|
||
package = VenomPackage.objects.filter(PackageUUID=package_uuid).first()
|
||
if package is None:
|
||
return _err('软件包不存在', 404)
|
||
rows = [_ReleaseRow(r) for r in package.releases.all()]
|
||
return _ok({'PackageName': package.PackageName, 'list': rows, 'total': len(rows)})
|
||
|
||
def post(self, request, package_uuid):
|
||
package = VenomPackage.objects.filter(PackageUUID=package_uuid).first()
|
||
if package is None:
|
||
return _err('软件包不存在', 404)
|
||
version = (request.data.get('Version') or '').strip()
|
||
if not version:
|
||
return _err('请填写版本号')
|
||
if len(version) > 64:
|
||
return _err('版本号过长(最多 64 字)')
|
||
release = VenomPackageRelease.objects.create(
|
||
Package=package,
|
||
Version=version,
|
||
Notes=request.data.get('Notes') or '',
|
||
PublisherName=(request.data.get('PublisherName') or '').strip()[:128],
|
||
)
|
||
return _ok(_ReleaseRow(release), msg='已发布')
|
||
|
||
|
||
class PackageByNameView(APIView):
|
||
"""按包名取包详情——官网 `venom.gvsds.com/project/<包名>/` 的数据源
|
||
|
||
GET /api/venom/project/<PackageName>/
|
||
"""
|
||
|
||
def get(self, request, package_name):
|
||
name = str(package_name or '').strip().strip('/')
|
||
package = VenomPackage.objects.annotate(release_count=Count('releases')).filter(
|
||
PackageName=name).first()
|
||
if package is None:
|
||
return _err(f'包「{name}」不存在', 404, status=404)
|
||
row = _DetailRow(package)
|
||
# 作者未填 README 时给出合成内容,官网不出现「无 README」占位
|
||
if not row['Readme']:
|
||
row['Readme'] = _DefaultReadme(package, list(package.releases.all()))
|
||
return _ok(row)
|