Coverage for distro_tracker/vendor/debian/tracker_panels.py: 90%

195 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2025-09-06 20:40 +0000

1# -*- coding: utf-8 -*- 

2 

3# Copyright 2013-2023 The Distro Tracker Developers 

4# See the COPYRIGHT file at the top-level directory of this distribution and 

5# at https://deb.li/DTAuthors 

6# 

7# This file is part of Distro Tracker. It is subject to the license terms 

8# in the LICENSE file found in the top-level directory of this 

9# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

10# including this file, may be copied, modified, propagated, or distributed 

11# except according to the terms contained in the LICENSE file. 

12"""Debian specific panels on the package page.""" 

13 

14from urllib.parse import quote, quote_plus 

15 

16from django.urls import reverse 

17from django.utils.encoding import force_str 

18from django.utils.functional import cached_property 

19from django.utils.http import urlencode 

20from django.utils.safestring import mark_safe 

21 

22from distro_tracker.core.models import ( 

23 PackageData, 

24 Repository, 

25 SourcePackageName 

26) 

27from distro_tracker.core.panels import ( 

28 BasePanel, 

29 HtmlPanelItem, 

30 LinksPanel, 

31 TemplatePanelItem 

32) 

33from distro_tracker.core.utils import get_or_none 

34from distro_tracker.core.utils.urls import RepologyPackagesUrl 

35from distro_tracker.vendor.debian.models import ( 

36 LintianStats, 

37 PackageExcuses, 

38 UbuntuPackage 

39) 

40 

41 

42class LintianLink(LinksPanel.ItemProvider): 

43 """ 

44 If there are any known lintian issues for the package, provides a link to 

45 the lintian page. 

46 """ 

47 def get_panel_items(self): 

48 try: 

49 lintian_stats = self.package.lintian_stats 

50 except LintianStats.DoesNotExist: 

51 return [] 

52 

53 if sum(lintian_stats.stats.values()): 

54 url = lintian_stats.get_lintian_url() 

55 return [ 

56 TemplatePanelItem('debian/lintian-link.html', { 

57 'lintian_stats': lintian_stats.stats, 

58 'lintian_url': url, 

59 }) 

60 ] 

61 

62 return [] 

63 

64 

65class BuildLogCheckLinks(LinksPanel.ItemProvider): 

66 def get_experimental_context(self): 

67 has_experimental = False 

68 experimental_repo = get_or_none(Repository, suite='experimental') 

69 if experimental_repo: 69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true

70 has_experimental = experimental_repo.has_source_package_name( 

71 self.package.name) 

72 return {'has_experimental': has_experimental} 

73 

74 def get_reproducible_context(self): 

75 try: 

76 infos = self.package.data.get(key='reproducibility') 

77 has_reproducibility = True 

78 reproducibility_status = infos.value['reproducibility'] 

79 except PackageData.DoesNotExist: 

80 has_reproducibility = False 

81 reproducibility_status = None 

82 reproducibility_url = \ 

83 "https://tests.reproducible-builds.org/debian/rb-pkg/{}.html" 

84 reproducibility_url = reproducibility_url.format( 

85 quote(self.package.name, safe="")) 

86 return {'has_reproducibility': has_reproducibility, 

87 'reproducibility_url': reproducibility_url, 

88 'reproducibility_status': reproducibility_status, 

89 } 

90 

91 def get_debcheck_context(self): 

92 # display debcheck link if there is at least one kind of problem 

93 has_debcheck = False 

94 for k in ['dependency_satisfaction', 

95 'builddependency_satisfaction']: 

96 try: 

97 self.package.data.get(key=k) 

98 has_debcheck = True 

99 break 

100 except PackageData.DoesNotExist: 

101 pass 

102 

103 debcheck_url = \ 

104 "https://qa.debian.org/dose/debcheck/src" \ 

105 "/{}.html".format(quote(self.package.name, safe="")) 

106 return {'has_debcheck': has_debcheck, 'debcheck_url': debcheck_url} 

107 

108 def get_crossqa_context(self): 

109 try: 

110 has_crossqa = False 

111 arches = self.package.data.get( 

112 key='general').value.get('architectures') 

113 # might be wrong due to https://bugs.debian.org/920024 

114 if arches is not None and arches != ['all']: 114 ↛ 115line 114 didn't jump to line 115, because the condition on line 114 was never true

115 has_crossqa = True 

116 except PackageData.DoesNotExist: 

117 has_crossqa = False 

118 return {'has_crossqa': has_crossqa} 

119 

120 def get_panel_items(self): 

121 if not isinstance(self.package, SourcePackageName): 

122 # Only source packages can have build log check info 

123 return 

124 

125 query_string = urlencode({'p': self.package.name}) 

126 

127 return [ 

128 TemplatePanelItem('debian/logcheck-links.html', { 

129 'package_name': quote(self.package.name), 

130 'package_query_string': query_string, 

131 **self.get_reproducible_context(), 

132 **self.get_experimental_context(), 

133 **self.get_debcheck_context(), 

134 **self.get_crossqa_context(), 

135 }) 

136 ] 

137 

138 

139class PopconLink(LinksPanel.ItemProvider): 

140 POPCON_URL = 'https://qa.debian.org/popcon.php?package={package}' 

141 

142 def get_panel_items(self): 

143 if not isinstance(self.package, SourcePackageName): 

144 return 

145 

146 return [ 

147 LinksPanel.SimpleLinkItem( 

148 'popcon', 

149 self.POPCON_URL.format( 

150 package=quote_plus(self.package.name))) 

151 ] 

152 

153 

154class SourceCodeSearchLinks(LinksPanel.ItemProvider): 

155 """ 

156 Add links to sources.debian.org source code browser and the 

157 codesearch.debian.net code search (if the package is found in unstable). 

158 """ 

159 #: A list of repositories that cause the sources.debian.org link to be 

160 #: displayed if the package is found in one of them. 

161 ALLOWED_REPOSITORIES = ( 

162 'unstable', 

163 'experimental', 

164 'testing', 

165 'stable', 

166 'oldstable', 

167 ) 

168 SOURCES_URL_TEMPLATE = 'https://sources.debian.org/src/{package}/{suite}/' 

169 SEARCH_FORM_TEMPLATE = ( 

170 '<form class="code-search-form"' 

171 ' action="' + reverse('dtracker-code-search') + '"' 

172 ' method="get" target="_blank">' 

173 '<input type="hidden" name="package" value="{package}">' 

174 '<input type="search" name="query" placeholder="search source code">' 

175 '</form>') 

176 

177 def get_panel_items(self): 

178 if not isinstance(self.package, SourcePackageName): 

179 # Only source packages can have these links 

180 return 

181 

182 repositories = [repo.suite for repo in self.package.repositories] + \ 

183 [repo.codename for repo in self.package.repositories] 

184 links = [] 

185 for allowed_repo in self.ALLOWED_REPOSITORIES: 

186 if allowed_repo in repositories: 

187 links.append(LinksPanel.SimpleLinkItem( 

188 'browse source code', 

189 self.SOURCES_URL_TEMPLATE.format( 

190 package=quote(self.package.name, safe=""), 

191 suite=quote(allowed_repo, safe="")))) 

192 break 

193 

194 if 'unstable' in repositories: 

195 # Add a search form 

196 links.append(HtmlPanelItem(self.SEARCH_FORM_TEMPLATE.format( 

197 package=self.package.name))) 

198 

199 return links 

200 

201 

202class DebtagsLink(LinksPanel.ItemProvider): 

203 """ 

204 Add a link to debtags editor. 

205 """ 

206 SOURCES_URL_TEMPLATE = \ 

207 'https://debtags.debian.org/rep/todo/maint/{maint}#{package}' 

208 

209 def get_panel_items(self): 

210 if not isinstance(self.package, SourcePackageName): 

211 return 

212 try: 

213 infos = self.package.data.get(key='general') 

214 except PackageData.DoesNotExist: 

215 return 

216 maintainer = infos.value['maintainer']['email'] 

217 return [ 

218 LinksPanel.SimpleLinkItem( 

219 'edit tags', 

220 self.SOURCES_URL_TEMPLATE.format( 

221 package=quote(self.package.name, safe=""), 

222 maint=quote(maintainer, safe="")) 

223 ) 

224 ] 

225 

226 

227class RepologyLink(LinksPanel.ItemProvider): 

228 """ 

229 Add a link to the Repology service. 

230 """ 

231 ALLOWED_REPOSITORIES = ( 

232 'unstable', 

233 'experimental', 

234 'testing', 

235 'stable-backports', 

236 'stable', 

237 'oldstable', 

238 ) 

239 

240 def get_panel_items(self): 

241 if not isinstance(self.package, SourcePackageName): 

242 # Only source packages can have these links 

243 return 

244 

245 suite = None 

246 repos = [repo.suite for repo in self.package.repositories] 

247 for repo in self.ALLOWED_REPOSITORIES: 

248 if repo in repos: 

249 suite = repo.replace('-', '_') 

250 break 

251 if suite is None: 

252 return 

253 return [ 

254 LinksPanel.SimpleLinkItem( 

255 'other distros', 

256 RepologyPackagesUrl( 

257 'debian_{suite}'.format(suite=suite), 

258 self.package.name 

259 ), 

260 'provided by Repology' 

261 ) 

262 ] 

263 

264 

265class SecurityTrackerLink(LinksPanel.ItemProvider): 

266 """ 

267 Add a link to the security tracker. 

268 """ 

269 URL_TEMPLATE = \ 

270 'https://security-tracker.debian.org/tracker/source-package/{package}' 

271 

272 def get_panel_items(self): 

273 if self.package.data.filter(key='debian-security').count() == 0: 273 ↛ 275line 273 didn't jump to line 275, because the condition on line 273 was never false

274 return 

275 return [ 

276 LinksPanel.SimpleLinkItem( 

277 'security tracker', 

278 self.URL_TEMPLATE.format(package=self.package.name) 

279 ) 

280 ] 

281 

282 

283class ScreenshotsLink(LinksPanel.ItemProvider): 

284 """ 

285 Add a link to screenshots.debian.net 

286 """ 

287 SOURCES_URL_TEMPLATE = \ 

288 'https://screenshots.debian.net/package/{package}' 

289 

290 def get_panel_items(self): 

291 if not isinstance(self.package, SourcePackageName): 

292 return 

293 try: 

294 infos = self.package.data.get(key='screenshots') 

295 except PackageData.DoesNotExist: 

296 return 

297 if infos.value['screenshots'] == 'true': 297 ↛ 306line 297 didn't jump to line 306, because the condition on line 297 was never false

298 return [ 

299 LinksPanel.SimpleLinkItem( 

300 'screenshots', 

301 self.SOURCES_URL_TEMPLATE.format( 

302 package=quote(self.package.name, safe="")) 

303 ) 

304 ] 

305 else: 

306 return 

307 

308 

309class TransitionsPanel(BasePanel): 

310 template_name = 'debian/transitions-panel.html' 

311 panel_importance = 2 

312 position = 'center' 

313 title = 'testing migrations' 

314 

315 @cached_property 

316 def context(self): 

317 try: 

318 excuses = self.package.excuses.excuses 

319 except PackageExcuses.DoesNotExist: 

320 excuses = None 

321 if excuses: 321 ↛ 322line 321 didn't jump to line 322, because the condition on line 321 was never true

322 excuses = [mark_safe(excuse) for excuse in excuses] 

323 return { 

324 'transitions': self.package.package_transitions.all(), 

325 'excuses': excuses, 

326 'package_name': self.package.name, 

327 } 

328 

329 @property 

330 def has_content(self): 

331 return bool(self.context['transitions']) or \ 

332 bool(self.context['excuses']) 

333 

334 

335class UbuntuPanel(BasePanel): 

336 template_name = 'debian/ubuntu-panel.html' 

337 position = 'right' 

338 title = 'ubuntu' 

339 

340 @cached_property 

341 def context(self): 

342 try: 

343 ubuntu_package = self.package.ubuntu_package 

344 except UbuntuPackage.DoesNotExist: 

345 return 

346 

347 return { 

348 'ubuntu_package': ubuntu_package, 

349 } 

350 

351 @property 

352 def has_content(self): 

353 return bool(self.context) 

354 

355 

356class BackToOldPTS(BasePanel): 

357 """ 

358 Display a message to users of the old PTS to encourage them to file bugs 

359 about issues that they discover and also to offer them a link back to the 

360 old PTS in case they need it. 

361 """ 

362 template_name = 'debian/back-to-old-pts.html' 

363 position = 'center' 

364 title = 'About the new package tracker' 

365 panel_importance = 100 

366 

367 @cached_property 

368 def context(self): 

369 return { 

370 'package': self.package.name 

371 } 

372 

373 @property 

374 def has_content(self): 

375 return "packages.qa.debian.org" in \ 

376 force_str(self.request.META.get('HTTP_REFERER', ''), 

377 encoding='latin1', errors='replace') 

378 

379 

380class Dl10nLinks(LinksPanel.ItemProvider): 

381 def get_panel_items(self): 

382 if not isinstance(self.package, SourcePackageName): 

383 return 

384 

385 try: 

386 dl10n_stats = self.package.data.get(key='dl10n').value 

387 except PackageData.DoesNotExist: 

388 return 

389 

390 return [ 

391 TemplatePanelItem('debian/dl10n-links.html', { 

392 'dl10n_stats': dl10n_stats, 

393 }) 

394 ] 

395 

396 

397class DebianPatchesLink(LinksPanel.ItemProvider): 

398 def get_panel_items(self): 

399 try: 

400 data = self.package.data.get(key='debian-patches').value 

401 except PackageData.DoesNotExist: 

402 return 

403 

404 count = data.get('patches', 0) 

405 if count == 0 or count is None: 

406 return 

407 

408 link_title = f'{count} patch' 

409 if count > 1: 409 ↛ 411line 409 didn't jump to line 411, because the condition on line 409 was never false

410 link_title += 'es' 

411 link_title += ' in debian/patches' 

412 

413 return [ 

414 LinksPanel.SimpleLinkItem('debian patches', data.get('url'), 

415 title=link_title), 

416 ]