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

184 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2025-10-07 08:16 +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 RepologyLink(LinksPanel.ItemProvider): 

203 """ 

204 Add a link to the Repology service. 

205 """ 

206 ALLOWED_REPOSITORIES = ( 

207 'unstable', 

208 'experimental', 

209 'testing', 

210 'stable-backports', 

211 'stable', 

212 'oldstable', 

213 ) 

214 

215 def get_panel_items(self): 

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

217 # Only source packages can have these links 

218 return 

219 

220 suite = None 

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

222 for repo in self.ALLOWED_REPOSITORIES: 

223 if repo in repos: 

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

225 break 

226 if suite is None: 

227 return 

228 return [ 

229 LinksPanel.SimpleLinkItem( 

230 'other distros', 

231 RepologyPackagesUrl( 

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

233 self.package.name 

234 ), 

235 'provided by Repology' 

236 ) 

237 ] 

238 

239 

240class SecurityTrackerLink(LinksPanel.ItemProvider): 

241 """ 

242 Add a link to the security tracker. 

243 """ 

244 URL_TEMPLATE = \ 

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

246 

247 def get_panel_items(self): 

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

249 return 

250 return [ 

251 LinksPanel.SimpleLinkItem( 

252 'security tracker', 

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

254 ) 

255 ] 

256 

257 

258class ScreenshotsLink(LinksPanel.ItemProvider): 

259 """ 

260 Add a link to screenshots.debian.net 

261 """ 

262 SOURCES_URL_TEMPLATE = \ 

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

264 

265 def get_panel_items(self): 

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

267 return 

268 try: 

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

270 except PackageData.DoesNotExist: 

271 return 

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

273 return [ 

274 LinksPanel.SimpleLinkItem( 

275 'screenshots', 

276 self.SOURCES_URL_TEMPLATE.format( 

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

278 ) 

279 ] 

280 else: 

281 return 

282 

283 

284class TransitionsPanel(BasePanel): 

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

286 panel_importance = 2 

287 position = 'center' 

288 title = 'testing migrations' 

289 

290 @cached_property 

291 def context(self): 

292 try: 

293 excuses = self.package.excuses.excuses 

294 except PackageExcuses.DoesNotExist: 

295 excuses = None 

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

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

298 return { 

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

300 'excuses': excuses, 

301 'package_name': self.package.name, 

302 } 

303 

304 @property 

305 def has_content(self): 

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

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

308 

309 

310class UbuntuPanel(BasePanel): 

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

312 position = 'right' 

313 title = 'ubuntu' 

314 

315 @cached_property 

316 def context(self): 

317 try: 

318 ubuntu_package = self.package.ubuntu_package 

319 except UbuntuPackage.DoesNotExist: 

320 return 

321 

322 return { 

323 'ubuntu_package': ubuntu_package, 

324 } 

325 

326 @property 

327 def has_content(self): 

328 return bool(self.context) 

329 

330 

331class BackToOldPTS(BasePanel): 

332 """ 

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

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

335 old PTS in case they need it. 

336 """ 

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

338 position = 'center' 

339 title = 'About the new package tracker' 

340 panel_importance = 100 

341 

342 @cached_property 

343 def context(self): 

344 return { 

345 'package': self.package.name 

346 } 

347 

348 @property 

349 def has_content(self): 

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

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

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

353 

354 

355class Dl10nLinks(LinksPanel.ItemProvider): 

356 def get_panel_items(self): 

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

358 return 

359 

360 try: 

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

362 except PackageData.DoesNotExist: 

363 return 

364 

365 return [ 

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

367 'dl10n_stats': dl10n_stats, 

368 }) 

369 ] 

370 

371 

372class DebianPatchesLink(LinksPanel.ItemProvider): 

373 def get_panel_items(self): 

374 try: 

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

376 except PackageData.DoesNotExist: 

377 return 

378 

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

380 if count == 0 or count is None: 

381 return 

382 

383 link_title = f'{count} patch' 

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

385 link_title += 'es' 

386 link_title += ' in debian/patches' 

387 

388 return [ 

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

390 title=link_title), 

391 ]