Coverage for distro_tracker/vendor/debian/tracker_panels.py: 90%
203 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-01-12 09:15 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-01-12 09:15 +0000
1# -*- coding: utf-8 -*-
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."""
14from urllib.parse import quote, quote_plus
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
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 BuildLogCheckStats,
37 LintianStats,
38 PackageExcuses,
39 UbuntuPackage
40)
43class LintianLink(LinksPanel.ItemProvider):
44 """
45 If there are any known lintian issues for the package, provides a link to
46 the lintian page.
47 """
48 def get_panel_items(self):
49 try:
50 lintian_stats = self.package.lintian_stats
51 except LintianStats.DoesNotExist:
52 return []
54 if sum(lintian_stats.stats.values()):
55 url = lintian_stats.get_lintian_url()
56 return [
57 TemplatePanelItem('debian/lintian-link.html', {
58 'lintian_stats': lintian_stats.stats,
59 'lintian_url': url,
60 })
61 ]
63 return []
66class BuildLogCheckLinks(LinksPanel.ItemProvider):
67 def get_experimental_context(self):
68 has_experimental = False
69 experimental_repo = get_or_none(Repository, suite='experimental')
70 if experimental_repo: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true
71 has_experimental = experimental_repo.has_source_package_name(
72 self.package.name)
73 return {'has_experimental': has_experimental}
75 def get_logcheck_context(self):
76 try:
77 self.package.build_logcheck_stats
78 has_checks = True
79 except BuildLogCheckStats.DoesNotExist:
80 has_checks = False
81 logcheck_url = \
82 "https://qa.debian.org/bls/packages/{hash}/{pkg}.html".format(
83 hash=quote(self.package.name[0], safe=""),
84 pkg=quote(self.package.name, safe=""))
85 return {'has_checks': has_checks, 'logcheck_url': logcheck_url}
87 def get_reproducible_context(self):
88 try:
89 infos = self.package.data.get(key='reproducibility')
90 has_reproducibility = True
91 reproducibility_status = infos.value['reproducibility']
92 except PackageData.DoesNotExist:
93 has_reproducibility = False
94 reproducibility_status = None
95 reproducibility_url = \
96 "https://tests.reproducible-builds.org/debian/rb-pkg/{}.html"
97 reproducibility_url = reproducibility_url.format(
98 quote(self.package.name, safe=""))
99 return {'has_reproducibility': has_reproducibility,
100 'reproducibility_url': reproducibility_url,
101 'reproducibility_status': reproducibility_status,
102 }
104 def get_debcheck_context(self):
105 # display debcheck link if there is at least one kind of problem
106 has_debcheck = False
107 for k in ['dependency_satisfaction',
108 'builddependency_satisfaction']:
109 try:
110 self.package.data.get(key=k)
111 has_debcheck = True
112 break
113 except PackageData.DoesNotExist:
114 pass
116 debcheck_url = \
117 "https://qa.debian.org/dose/debcheck/src" \
118 "/{}.html".format(quote(self.package.name, safe=""))
119 return {'has_debcheck': has_debcheck, 'debcheck_url': debcheck_url}
121 def get_crossqa_context(self):
122 try:
123 has_crossqa = False
124 arches = self.package.data.get(
125 key='general').value.get('architectures')
126 # might be wrong due to https://bugs.debian.org/920024
127 if arches is not None and arches != ['all']: 127 ↛ 128line 127 didn't jump to line 128, because the condition on line 127 was never true
128 has_crossqa = True
129 except PackageData.DoesNotExist:
130 has_crossqa = False
131 return {'has_crossqa': has_crossqa}
133 def get_panel_items(self):
134 if not isinstance(self.package, SourcePackageName):
135 # Only source packages can have build log check info
136 return
138 query_string = urlencode({'p': self.package.name})
140 return [
141 TemplatePanelItem('debian/logcheck-links.html', {
142 'package_name': quote(self.package.name),
143 'package_query_string': query_string,
144 **self.get_logcheck_context(),
145 **self.get_reproducible_context(),
146 **self.get_experimental_context(),
147 **self.get_debcheck_context(),
148 **self.get_crossqa_context(),
149 })
150 ]
153class PopconLink(LinksPanel.ItemProvider):
154 POPCON_URL = 'https://qa.debian.org/popcon.php?package={package}'
156 def get_panel_items(self):
157 if not isinstance(self.package, SourcePackageName):
158 return
160 return [
161 LinksPanel.SimpleLinkItem(
162 'popcon',
163 self.POPCON_URL.format(
164 package=quote_plus(self.package.name)))
165 ]
168class SourceCodeSearchLinks(LinksPanel.ItemProvider):
169 """
170 Add links to sources.debian.org source code browser and the
171 codesearch.debian.net code search (if the package is found in unstable).
172 """
173 #: A list of repositories that cause the sources.debian.org link to be
174 #: displayed if the package is found in one of them.
175 ALLOWED_REPOSITORIES = (
176 'unstable',
177 'experimental',
178 'testing',
179 'stable',
180 'oldstable',
181 )
182 SOURCES_URL_TEMPLATE = 'https://sources.debian.org/src/{package}/{suite}/'
183 SEARCH_FORM_TEMPLATE = (
184 '<form class="code-search-form"'
185 ' action="' + reverse('dtracker-code-search') + '"'
186 ' method="get" target="_blank">'
187 '<input type="hidden" name="package" value="{package}">'
188 '<input type="search" name="query" placeholder="search source code">'
189 '</form>')
191 def get_panel_items(self):
192 if not isinstance(self.package, SourcePackageName):
193 # Only source packages can have these links
194 return
196 repositories = [repo.suite for repo in self.package.repositories] + \
197 [repo.codename for repo in self.package.repositories]
198 links = []
199 for allowed_repo in self.ALLOWED_REPOSITORIES:
200 if allowed_repo in repositories:
201 links.append(LinksPanel.SimpleLinkItem(
202 'browse source code',
203 self.SOURCES_URL_TEMPLATE.format(
204 package=quote(self.package.name, safe=""),
205 suite=quote(allowed_repo, safe=""))))
206 break
208 if 'unstable' in repositories:
209 # Add a search form
210 links.append(HtmlPanelItem(self.SEARCH_FORM_TEMPLATE.format(
211 package=self.package.name)))
213 return links
216class DebtagsLink(LinksPanel.ItemProvider):
217 """
218 Add a link to debtags editor.
219 """
220 SOURCES_URL_TEMPLATE = \
221 'https://debtags.debian.org/rep/todo/maint/{maint}#{package}'
223 def get_panel_items(self):
224 if not isinstance(self.package, SourcePackageName):
225 return
226 try:
227 infos = self.package.data.get(key='general')
228 except PackageData.DoesNotExist:
229 return
230 maintainer = infos.value['maintainer']['email']
231 return [
232 LinksPanel.SimpleLinkItem(
233 'edit tags',
234 self.SOURCES_URL_TEMPLATE.format(
235 package=quote(self.package.name, safe=""),
236 maint=quote(maintainer, safe=""))
237 )
238 ]
241class RepologyLink(LinksPanel.ItemProvider):
242 """
243 Add a link to the Repology service.
244 """
245 ALLOWED_REPOSITORIES = (
246 'unstable',
247 'experimental',
248 'testing',
249 'stable-backports',
250 'stable',
251 'oldstable',
252 )
254 def get_panel_items(self):
255 if not isinstance(self.package, SourcePackageName):
256 # Only source packages can have these links
257 return
259 suite = None
260 repos = [repo.suite for repo in self.package.repositories]
261 for repo in self.ALLOWED_REPOSITORIES:
262 if repo in repos:
263 suite = repo.replace('-', '_')
264 break
265 if suite is None:
266 return
267 return [
268 LinksPanel.SimpleLinkItem(
269 'other distros',
270 RepologyPackagesUrl(
271 'debian_{suite}'.format(suite=suite),
272 self.package.name
273 ),
274 'provided by Repology'
275 )
276 ]
279class SecurityTrackerLink(LinksPanel.ItemProvider):
280 """
281 Add a link to the security tracker.
282 """
283 URL_TEMPLATE = \
284 'https://security-tracker.debian.org/tracker/source-package/{package}'
286 def get_panel_items(self):
287 if self.package.data.filter(key='debian-security').count() == 0: 287 ↛ 289line 287 didn't jump to line 289, because the condition on line 287 was never false
288 return
289 return [
290 LinksPanel.SimpleLinkItem(
291 'security tracker',
292 self.URL_TEMPLATE.format(package=self.package.name)
293 )
294 ]
297class ScreenshotsLink(LinksPanel.ItemProvider):
298 """
299 Add a link to screenshots.debian.net
300 """
301 SOURCES_URL_TEMPLATE = \
302 'https://screenshots.debian.net/package/{package}'
304 def get_panel_items(self):
305 if not isinstance(self.package, SourcePackageName):
306 return
307 try:
308 infos = self.package.data.get(key='screenshots')
309 except PackageData.DoesNotExist:
310 return
311 if infos.value['screenshots'] == 'true': 311 ↛ 320line 311 didn't jump to line 320, because the condition on line 311 was never false
312 return [
313 LinksPanel.SimpleLinkItem(
314 'screenshots',
315 self.SOURCES_URL_TEMPLATE.format(
316 package=quote(self.package.name, safe=""))
317 )
318 ]
319 else:
320 return
323class TransitionsPanel(BasePanel):
324 template_name = 'debian/transitions-panel.html'
325 panel_importance = 2
326 position = 'center'
327 title = 'testing migrations'
329 @cached_property
330 def context(self):
331 try:
332 excuses = self.package.excuses.excuses
333 except PackageExcuses.DoesNotExist:
334 excuses = None
335 if excuses: 335 ↛ 336line 335 didn't jump to line 336, because the condition on line 335 was never true
336 excuses = [mark_safe(excuse) for excuse in excuses]
337 return {
338 'transitions': self.package.package_transitions.all(),
339 'excuses': excuses,
340 'package_name': self.package.name,
341 }
343 @property
344 def has_content(self):
345 return bool(self.context['transitions']) or \
346 bool(self.context['excuses'])
349class UbuntuPanel(BasePanel):
350 template_name = 'debian/ubuntu-panel.html'
351 position = 'right'
352 title = 'ubuntu'
354 @cached_property
355 def context(self):
356 try:
357 ubuntu_package = self.package.ubuntu_package
358 except UbuntuPackage.DoesNotExist:
359 return
361 return {
362 'ubuntu_package': ubuntu_package,
363 }
365 @property
366 def has_content(self):
367 return bool(self.context)
370class BackToOldPTS(BasePanel):
371 """
372 Display a message to users of the old PTS to encourage them to file bugs
373 about issues that they discover and also to offer them a link back to the
374 old PTS in case they need it.
375 """
376 template_name = 'debian/back-to-old-pts.html'
377 position = 'center'
378 title = 'About the new package tracker'
379 panel_importance = 100
381 @cached_property
382 def context(self):
383 return {
384 'package': self.package.name
385 }
387 @property
388 def has_content(self):
389 return "packages.qa.debian.org" in \
390 force_str(self.request.META.get('HTTP_REFERER', ''),
391 encoding='latin1', errors='replace')
394class Dl10nLinks(LinksPanel.ItemProvider):
395 def get_panel_items(self):
396 if not isinstance(self.package, SourcePackageName):
397 return
399 try:
400 dl10n_stats = self.package.data.get(key='dl10n').value
401 except PackageData.DoesNotExist:
402 return
404 return [
405 TemplatePanelItem('debian/dl10n-links.html', {
406 'dl10n_stats': dl10n_stats,
407 })
408 ]
411class DebianPatchesLink(LinksPanel.ItemProvider):
412 def get_panel_items(self):
413 try:
414 data = self.package.data.get(key='debian-patches').value
415 except PackageData.DoesNotExist:
416 return
418 count = data.get('patches', 0)
419 if count == 0 or count is None:
420 return
422 link_title = f'{count} patch'
423 if count > 1: 423 ↛ 425line 423 didn't jump to line 425, because the condition on line 423 was never false
424 link_title += 'es'
425 link_title += ' in debian/patches'
427 return [
428 LinksPanel.SimpleLinkItem('debian patches', data.get('url'),
429 title=link_title),
430 ]