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

2 

3# Copyright 2013 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 django.urls import reverse 

15from django.utils.encoding import force_text 

16from django.utils.functional import cached_property 

17from django.utils.http import urlencode, urlquote, urlquote_plus 

18from django.utils.safestring import mark_safe 

19 

20from distro_tracker.core.models import ( 

21 PackageData, 

22 Repository, 

23 SourcePackageName 

24) 

25from distro_tracker.core.panels import ( 

26 BasePanel, 

27 HtmlPanelItem, 

28 LinksPanel, 

29 TemplatePanelItem 

30) 

31from distro_tracker.core.utils import get_or_none 

32from distro_tracker.core.utils.urls import RepologyPackagesUrl 

33from distro_tracker.vendor.debian.models import ( 

34 BuildLogCheckStats, 

35 LintianStats, 

36 PackageExcuses, 

37 UbuntuPackage 

38) 

39 

40 

41class LintianLink(LinksPanel.ItemProvider): 

42 """ 

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

44 the lintian page. 

45 """ 

46 def get_panel_items(self): 

47 try: 

48 lintian_stats = self.package.lintian_stats 

49 except LintianStats.DoesNotExist: 

50 return [] 

51 

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

53 url = lintian_stats.get_lintian_url() 

54 return [ 

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

56 'lintian_stats': lintian_stats.stats, 

57 'lintian_url': url, 

58 }) 

59 ] 

60 

61 return [] 

62 

63 

64class BuildLogCheckLinks(LinksPanel.ItemProvider): 

65 def get_experimental_context(self): 

66 has_experimental = False 

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

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

69 has_experimental = experimental_repo.has_source_package_name( 

70 self.package.name) 

71 return {'has_experimental': has_experimental} 

72 

73 def get_logcheck_context(self): 

74 try: 

75 self.package.build_logcheck_stats 

76 has_checks = True 

77 except BuildLogCheckStats.DoesNotExist: 

78 has_checks = False 

79 logcheck_url = \ 

80 "https://qa.debian.org/bls/packages/{hash}/{pkg}.html".format( 

81 hash=urlquote(self.package.name[0], safe=""), 

82 pkg=urlquote(self.package.name, safe="")) 

83 return {'has_checks': has_checks, 'logcheck_url': logcheck_url} 

84 

85 def get_reproducible_context(self): 

86 try: 

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

88 has_reproducibility = True 

89 reproducibility_status = infos.value['reproducibility'] 

90 except PackageData.DoesNotExist: 

91 has_reproducibility = False 

92 reproducibility_status = None 

93 reproducibility_url = \ 

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

95 reproducibility_url = reproducibility_url.format( 

96 urlquote(self.package.name, safe="")) 

97 return {'has_reproducibility': has_reproducibility, 

98 'reproducibility_url': reproducibility_url, 

99 'reproducibility_status': reproducibility_status, 

100 } 

101 

102 def get_debcheck_context(self): 

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

104 has_debcheck = False 

105 for k in ['dependency_satisfaction', 

106 'builddependency_satisfaction']: 

107 try: 

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

109 has_debcheck = True 

110 break 

111 except PackageData.DoesNotExist: 

112 pass 

113 

114 debcheck_url = \ 

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

116 "/{}.html".format(urlquote(self.package.name, safe="")) 

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

118 

119 def get_crossqa_context(self): 

120 try: 

121 has_crossqa = False 

122 arches = self.package.data.get( 

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

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

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

126 has_crossqa = True 

127 except PackageData.DoesNotExist: 

128 has_crossqa = False 

129 return {'has_crossqa': has_crossqa} 

130 

131 def get_panel_items(self): 

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

133 # Only source packages can have build log check info 

134 return 

135 

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

137 

138 return [ 

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

140 'package_name': urlquote(self.package.name), 

141 'package_query_string': query_string, 

142 **self.get_logcheck_context(), 

143 **self.get_reproducible_context(), 

144 **self.get_experimental_context(), 

145 **self.get_debcheck_context(), 

146 **self.get_crossqa_context(), 

147 }) 

148 ] 

149 

150 

151class PopconLink(LinksPanel.ItemProvider): 

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

153 

154 def get_panel_items(self): 

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

156 return 

157 

158 return [ 

159 LinksPanel.SimpleLinkItem( 

160 'popcon', 

161 self.POPCON_URL.format( 

162 package=urlquote_plus(self.package.name))) 

163 ] 

164 

165 

166class SourceCodeSearchLinks(LinksPanel.ItemProvider): 

167 """ 

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

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

170 """ 

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

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

173 ALLOWED_REPOSITORIES = ( 

174 'unstable', 

175 'experimental', 

176 'testing', 

177 'stable', 

178 'oldstable', 

179 ) 

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

181 SEARCH_FORM_TEMPLATE = ( 

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

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

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

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

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

187 '</form>') 

188 

189 def get_panel_items(self): 

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

191 # Only source packages can have these links 

192 return 

193 

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

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

196 links = [] 

197 for allowed_repo in self.ALLOWED_REPOSITORIES: 

198 if allowed_repo in repositories: 

199 links.append(LinksPanel.SimpleLinkItem( 

200 'browse source code', 

201 self.SOURCES_URL_TEMPLATE.format( 

202 package=urlquote(self.package.name, safe=""), 

203 suite=urlquote(allowed_repo, safe="")))) 

204 break 

205 

206 if 'unstable' in repositories: 

207 # Add a search form 

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

209 package=self.package.name))) 

210 

211 return links 

212 

213 

214class DebtagsLink(LinksPanel.ItemProvider): 

215 """ 

216 Add a link to debtags editor. 

217 """ 

218 SOURCES_URL_TEMPLATE = \ 

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

220 

221 def get_panel_items(self): 

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

223 return 

224 try: 

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

226 except PackageData.DoesNotExist: 

227 return 

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

229 return [ 

230 LinksPanel.SimpleLinkItem( 

231 'edit tags', 

232 self.SOURCES_URL_TEMPLATE.format( 

233 package=urlquote(self.package.name, safe=""), 

234 maint=urlquote(maintainer, safe="")) 

235 ) 

236 ] 

237 

238 

239class RepologyLink(LinksPanel.ItemProvider): 

240 """ 

241 Add a link to the Repology service. 

242 """ 

243 ALLOWED_REPOSITORIES = ( 

244 'unstable', 

245 'experimental', 

246 'testing', 

247 'stable-backports', 

248 'stable', 

249 'oldstable', 

250 ) 

251 

252 def get_panel_items(self): 

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

254 # Only source packages can have these links 

255 return 

256 

257 suite = None 

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

259 for repo in self.ALLOWED_REPOSITORIES: 

260 if repo in repos: 

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

262 break 

263 if suite is None: 

264 return 

265 return [ 

266 LinksPanel.SimpleLinkItem( 

267 'other distros', 

268 RepologyPackagesUrl( 

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

270 self.package.name 

271 ), 

272 'provided by Repology' 

273 ) 

274 ] 

275 

276 

277class SecurityTrackerLink(LinksPanel.ItemProvider): 

278 """ 

279 Add a link to the security tracker. 

280 """ 

281 URL_TEMPLATE = \ 

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

283 

284 def get_panel_items(self): 

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

286 return 

287 return [ 

288 LinksPanel.SimpleLinkItem( 

289 'security tracker', 

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

291 ) 

292 ] 

293 

294 

295class ScreenshotsLink(LinksPanel.ItemProvider): 

296 """ 

297 Add a link to screenshots.debian.net 

298 """ 

299 SOURCES_URL_TEMPLATE = \ 

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

301 

302 def get_panel_items(self): 

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

304 return 

305 try: 

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

307 except PackageData.DoesNotExist: 

308 return 

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

310 return [ 

311 LinksPanel.SimpleLinkItem( 

312 'screenshots', 

313 self.SOURCES_URL_TEMPLATE.format( 

314 package=urlquote(self.package.name, safe="")) 

315 ) 

316 ] 

317 else: 

318 return 

319 

320 

321class TransitionsPanel(BasePanel): 

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

323 panel_importance = 2 

324 position = 'center' 

325 title = 'testing migrations' 

326 

327 @cached_property 

328 def context(self): 

329 try: 

330 excuses = self.package.excuses.excuses 

331 except PackageExcuses.DoesNotExist: 

332 excuses = None 

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

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

335 return { 

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

337 'excuses': excuses, 

338 'package_name': self.package.name, 

339 } 

340 

341 @property 

342 def has_content(self): 

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

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

345 

346 

347class UbuntuPanel(BasePanel): 

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

349 position = 'right' 

350 title = 'ubuntu' 

351 

352 @cached_property 

353 def context(self): 

354 try: 

355 ubuntu_package = self.package.ubuntu_package 

356 except UbuntuPackage.DoesNotExist: 

357 return 

358 

359 return { 

360 'ubuntu_package': ubuntu_package, 

361 } 

362 

363 @property 

364 def has_content(self): 

365 return bool(self.context) 

366 

367 

368class BackToOldPTS(BasePanel): 

369 """ 

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

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

372 old PTS in case they need it. 

373 """ 

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

375 position = 'center' 

376 title = 'About the new package tracker' 

377 panel_importance = 100 

378 

379 @cached_property 

380 def context(self): 

381 return { 

382 'package': self.package.name 

383 } 

384 

385 @property 

386 def has_content(self): 

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

388 force_text(self.request.META.get('HTTP_REFERER', ''), 

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

390 

391 

392class Dl10nLinks(LinksPanel.ItemProvider): 

393 def get_panel_items(self): 

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

395 return 

396 

397 try: 

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

399 except PackageData.DoesNotExist: 

400 return 

401 

402 return [ 

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

404 'dl10n_stats': dl10n_stats, 

405 }) 

406 ]