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
« prev ^ index » next coverage.py v6.5.0, created at 2025-09-06 20:40 +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 LintianStats,
37 PackageExcuses,
38 UbuntuPackage
39)
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 []
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 ]
62 return []
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}
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 }
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
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}
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}
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
125 query_string = urlencode({'p': self.package.name})
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 ]
139class PopconLink(LinksPanel.ItemProvider):
140 POPCON_URL = 'https://qa.debian.org/popcon.php?package={package}'
142 def get_panel_items(self):
143 if not isinstance(self.package, SourcePackageName):
144 return
146 return [
147 LinksPanel.SimpleLinkItem(
148 'popcon',
149 self.POPCON_URL.format(
150 package=quote_plus(self.package.name)))
151 ]
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>')
177 def get_panel_items(self):
178 if not isinstance(self.package, SourcePackageName):
179 # Only source packages can have these links
180 return
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
194 if 'unstable' in repositories:
195 # Add a search form
196 links.append(HtmlPanelItem(self.SEARCH_FORM_TEMPLATE.format(
197 package=self.package.name)))
199 return links
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}'
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 ]
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 )
240 def get_panel_items(self):
241 if not isinstance(self.package, SourcePackageName):
242 # Only source packages can have these links
243 return
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 ]
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}'
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 ]
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}'
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
309class TransitionsPanel(BasePanel):
310 template_name = 'debian/transitions-panel.html'
311 panel_importance = 2
312 position = 'center'
313 title = 'testing migrations'
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 }
329 @property
330 def has_content(self):
331 return bool(self.context['transitions']) or \
332 bool(self.context['excuses'])
335class UbuntuPanel(BasePanel):
336 template_name = 'debian/ubuntu-panel.html'
337 position = 'right'
338 title = 'ubuntu'
340 @cached_property
341 def context(self):
342 try:
343 ubuntu_package = self.package.ubuntu_package
344 except UbuntuPackage.DoesNotExist:
345 return
347 return {
348 'ubuntu_package': ubuntu_package,
349 }
351 @property
352 def has_content(self):
353 return bool(self.context)
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
367 @cached_property
368 def context(self):
369 return {
370 'package': self.package.name
371 }
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')
380class Dl10nLinks(LinksPanel.ItemProvider):
381 def get_panel_items(self):
382 if not isinstance(self.package, SourcePackageName):
383 return
385 try:
386 dl10n_stats = self.package.data.get(key='dl10n').value
387 except PackageData.DoesNotExist:
388 return
390 return [
391 TemplatePanelItem('debian/dl10n-links.html', {
392 'dl10n_stats': dl10n_stats,
393 })
394 ]
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
404 count = data.get('patches', 0)
405 if count == 0 or count is None:
406 return
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'
413 return [
414 LinksPanel.SimpleLinkItem('debian patches', data.get('url'),
415 title=link_title),
416 ]