"""模块:Venom 包管理器 API 远端页面(由本服务托管、在 GERP 宿主内渲染)跨源调用以下接口: GET /api/venom/packages/ 包列表 POST /api/venom/packages/ 新建包(包名全局唯一) GET /api/venom/packages// 包详情(配置页读取) POST /api/venom/packages//config/ 保存包配置 GET /api/venom/packages//releases/ 发布历史 POST /api/venom/packages//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// """ 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)