58 Commits

Author SHA1 Message Date
Jelle van der Waa
b814ab4d72 Update django to the latest version
All checks were successful
Github-Actions / build (push) Successful in 2m44s
2025-10-02 11:30:53 +02:00
Jelle van der Waa
a0ec59ddc3 main: render package maintainers pgp keys correctly
We need to allow html to be passed into format_html.
2025-09-18 18:00:44 +02:00
Lukas Fleischer
70e796acc3 Fix logout link formatting
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
2025-09-17 21:14:21 +02:00
Jelle van der Waa
242a126245 Update to Django 5.1.12 2025-09-17 21:13:11 +02:00
Jakub Klinkovský
03a0e748ec Fix format string for package links in todo lists
Fixes 0cf24055d5

Fixes https://github.com/archlinux/archweb/issues/582
2025-09-17 09:33:24 +02:00
Jelle van der Waa
fb9b330535 Drop shells.com as sponsor 2025-09-15 21:47:23 +02:00
Ben Mezger
6bc1b49071 Fix Django schema deprecation 2025-09-03 10:54:09 +02:00
Ben Mezger
0cf24055d5 Update deprecated format_html 2025-09-03 10:54:09 +02:00
Sven-Hendrik Haase
59229f280b Add link to new Dev Blog in Development section
All checks were successful
Github-Actions / build (push) Successful in 3m5s
2025-07-24 16:14:44 +02:00
Jelle van der Waa
4c57725862 treewide: port away from deprecated datetime.utcnow() 2025-07-22 18:32:21 +02:00
Jelle van der Waa
8ded12a7cf Update Django to 5.1.10 2025-07-22 18:32:21 +02:00
Daniel M. Capella
0180242d2a todolists: include last_modified in template
Closes https://github.com/archlinux/archweb/issues/551
2025-07-13 10:21:59 +02:00
Daniel M. Capella
bf29716008 signoffs: remove superfluous conditional
The page is only accessible if you're logged in.
2025-07-13 10:21:59 +02:00
Daniel M. Capella
2027adaaba signoffs: add "Only Mine" filter
Implements https://github.com/archlinux/archweb/issues/550
2025-06-21 12:11:44 +02:00
dependabot[bot]
05abc7f386 build(deps): bump requests from 2.32.3 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 14:15:11 +02:00
nl6720
c1d70301c9 templates/public/download.html: remove gpg keyserver-options
The public key is retrieved via WKD in the previous command, so there is
no reason to contact any keyservers.
2025-06-01 09:42:51 +02:00
Levente Polyak
323002ffe1 donate: remove PIA as the active sponsorship has stopped 2025-05-26 09:26:40 +02:00
Levente Polyak
a49a2cadc5 donate: remove UptimeRobot as they stopped sponsoring us 2025-05-26 09:26:40 +02:00
Marcus B Spencer
a733efa173 Fix typo on "Multilib Differences to Main Packages" page
"x864_" is a typo of "x86_64".

https://archlinux.org/packages/differences
2025-05-19 09:10:07 +02:00
Jelle van der Waa
ce776a6297 Update to latest django LTS release 2025-05-12 20:18:11 +02:00
Kristian Klausen
7126b7b006 public: Remove Vagrant images
We are no longer releasing Vagrant images[1].

[1] 3f7b895725
2025-05-12 20:02:37 +02:00
Jelle van der Waa
ba4eb27ae3 Drop UnixStickers from support 2025-05-12 20:01:19 +02:00
Wiktor Kwapisiewicz
375713717a Add social field to user's profiles 2025-05-12 20:00:40 +02:00
Luca Weiss
5e33be47a7 bump django to 5.0.14
All checks were successful
Github-Actions / build (push) Successful in 2m33s
2025-04-17 10:05:42 +02:00
Robin Candau
4473a9cdba Add the automated install / run method for the WSL image to the Download page 2025-04-15 10:18:56 +02:00
Robin Candau
24b21bffed Point to the wiki page in the WSL images download section
More informative than the GitLab repo's README
2025-04-12 15:23:40 +02:00
Robin Candau
7f5d7e9082 Add WSL images to the download page
https://geo.mirror.pkgbuild.com/wsl/latest
https://gitlab.archlinux.org/archlinux/archlinux-wsl/
2025-04-12 15:14:54 +02:00
Jelle van der Waa
653b482ec5 Include request scheme into opensearch data 2025-03-29 19:08:16 +01:00
Jelle van der Waa
ecc4bdf0a7 bump django to 5.0.13 2025-03-29 18:55:35 +01:00
luis.carilla
a45b88da4b fix linting issue 2025-03-29 15:13:22 +01:00
luis.carilla
e460ba4727 set a timeout for the rsync subprocess when checking mirror availability 2025-03-29 15:13:22 +01:00
Jelle van der Waa
910e428baa public: update to latest seqouia version
Closes: #553
2025-02-20 17:53:50 +01:00
Christian Heusel
f624f5677b donate: Add link to LoadView
This was forgotten in the previous agreement with that sponsor.

Signed-off-by: Christian Heusel <christian@heusel.eu>
2025-02-12 10:56:53 +01:00
luzpaz
2064099696 Fix typos
Some checks failed
Github-Actions / build (push) Failing after 2m25s
2025-01-30 10:32:52 +01:00
Jelle van der Waa
67209075c5 devel: fix grammar mistake a HTTP => an HTTP
Some checks failed
Github-Actions / build (push) Failing after 10s
Closes: #546
2025-01-28 10:13:01 +01:00
Jelle van der Waa
336d686ca2 public: add spacing between past donors and a past donor
Some checks failed
Github-Actions / build (push) Failing after 7s
2025-01-25 12:10:38 +01:00
Christian Heusel
4f0e24f1f7 donate: Add link to DotcomMonitor
Signed-off-by: Christian Heusel <christian@heusel.eu>
2025-01-25 11:55:13 +01:00
Jelle van der Waa
e07054c8ea .github: run ruff in CI
Some checks failed
Github-Actions / build (push) Failing after 56s
2025-01-18 16:17:17 +01:00
Jelle van der Waa
97aae09dce flake8: limit line length to 118
Apply the same limit as the ruff configuration. flake8 is still used as
the relevant E* rules are still in preview mode.
2025-01-18 16:17:17 +01:00
Jelle van der Waa
0ce1a0ea5f main: fix last remaining line length violation 2025-01-18 16:17:17 +01:00
Jelle van der Waa
f38770be76 ruff.toml: ignore cssmin code 2025-01-18 16:17:17 +01:00
Jelle van der Waa
2d39dc6379 bump Python version to 3.13 2025-01-18 16:17:17 +01:00
Jelle van der Waa
df4b0bfd67 Fix domain being None in opensearch results
The domain came from HTTP_HOST which in our nginx configuration is not
set, furthermore other code already uses Site to obtain the domain.

Closes: #541
2025-01-18 13:18:51 +01:00
Jelle van der Waa
5da7fa80c5 treewide: reduce line length to 118 2025-01-18 12:42:02 +01:00
Jelle van der Waa
8d495d4fa7 planet: reduce line length to 118 2025-01-18 12:42:02 +01:00
Jelle van der Waa
796c3f410f todolists: reduce line length to 118 2025-01-18 12:42:02 +01:00
dependabot[bot]
37687bf9e4 build(deps): bump django from 5.0.10 to 5.0.11
Bumps [django](https://github.com/django/django) from 5.0.10 to 5.0.11.
- [Commits](https://github.com/django/django/compare/5.0.10...5.0.11)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 09:48:13 +01:00
Jelle van der Waa
2bf2fa235f Update Python dependencies
Some checks failed
Github-Actions / build (push) Failing after 54s
2025-01-11 17:44:02 +01:00
Jakub Klinkovský
87a0d37953 Populate signoffs with a commit message of the tag in extra-testing, not the latest commit
Fixes #533
2025-01-11 17:35:32 +01:00
Jakub Klinkovský
4210a46f9a Add staging repos to fixtures
Without this we cannot populate the database with real staging packages,
which might be useful for testing something.
2025-01-11 17:35:32 +01:00
Jakub Klinkovský
bd4d50b84e Improve the "zebra" style of dynamic tables where rows can be dynamically hidden 2025-01-11 17:31:27 +01:00
nl6720
fcd473608c Use ISO 8601 date format
Use the `Y-m-d` format everywhere.

Fixes https://github.com/archlinux/archweb/issues/520
2025-01-11 17:21:05 +01:00
dependabot[bot]
4e25b014e4 build(deps): bump django from 5.0.9 to 5.0.10
Bumps [django](https://github.com/django/django) from 5.0.9 to 5.0.10.
- [Commits](https://github.com/django/django/compare/5.0.9...5.0.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-07 11:32:14 +01:00
dependabot[bot]
c15b22203a build(deps): bump django from 5.0.8 to 5.0.9
Some checks failed
Github-Actions / build (push) Failing after 8s
Bumps [django](https://github.com/django/django) from 5.0.8 to 5.0.9.
- [Commits](https://github.com/django/django/compare/5.0.8...5.0.9)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-09 09:32:10 +02:00
nl6720
b39c4c9385 Use ISO 8601 date format
Explicitly specify the ISO 8601 format `Y-m-d H:i` in the date template.

Django 5.0.5 removed the USE_L10N setting making the locale-specific
formats override DATE_FORMAT and DATETIME_FORMAT. See
https://forum.djangoproject.com/t/datetime-format/30811/10 for details.

Fixes https://github.com/archlinux/archweb/issues/520
2024-10-01 12:07:28 +02:00
nl6720
3da96a4007 Update package issue tracker link in "Flag Package" page
Some checks failed
Github-Actions / build (push) Failing after 10s
Link to the package's issue tracker on gitlab.archlinux.org instead of
the old bug tracker.
2024-09-26 14:53:40 +02:00
Christian Heusel
fc191bbe84 enhancement: add transparent background to sponsor images 2024-09-14 11:22:27 +02:00
Jelle van der Waa
c68204d6bb public: make bittorrent and iso downloads from mirrors more visible 2024-09-14 11:14:52 +02:00
69 changed files with 317 additions and 192 deletions

View File

@@ -1,3 +1,3 @@
[flake8]
max-line-length = 300
max-line-length = 118
ignore = E731, E241, E741

View File

@@ -9,14 +9,17 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: 3.11
python-version: 3.13
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt && pip install -r requirements_test.txt
pip install -r requirements.txt && pip install -r requirements_test.txt && pip install ruff
- name: Run ruff
run: |
ruff check .
- name: Lint with flake8
run: |
make lint

View File

@@ -131,7 +131,7 @@ Archweb provides multiple management commands for importing various sorts of dat
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
* donor_import - Import a single donator from a mail passed to stdin
* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
* mirrorresolv - Poll every active mirror URLs and determine wheteher they have IP4 and/or IPv6 addresses.
* mirrorresolv - Poll every active mirror URLs and determine whether they have IP4 and/or IPv6 addresses.
* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
* update_planet - Import all feeds for users who have a valid website and website_rss in their user profile.
* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.

View File

@@ -71,7 +71,8 @@ class Database(object):
retry = False
except OperationalError as exc:
retry_count += 1
logger.error('Unable to update database \'%s\', retrying=%d', self.path, retry_count, exc_info=exc)
logger.error('Unable to update database \'%s\', retrying=%d',
self.path, retry_count, exc_info=exc)
time.sleep(5)
if retry_count == self.retry_limit:

View File

@@ -59,7 +59,7 @@ class Command(BaseCommand):
arches = Arch.objects.filter(agnostic=False)
repos = Repo.objects.all()
arch_path_map = {arch: None for arch in arches}
arch_path_map = dict.fromkeys(arches)
all_paths = set()
total_paths = 0
for arch in arches:
@@ -89,7 +89,9 @@ class Command(BaseCommand):
for name in all_paths:
manager.add_watch(name, mask)
handler = EventHandler(arch_paths=arch_path_map, filename_suffix='.links.tar.gz', callback_func=wrapper_read_links)
handler = EventHandler(arch_paths=arch_path_map,
filename_suffix='.links.tar.gz',
callback_func=wrapper_read_links)
return pyinotify.Notifier(manager, handler)

View File

@@ -539,7 +539,9 @@ def parse_info(pkgname, filename, iofile):
elif blockname:
store[blockname].append(line)
else:
raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname, filename, line))
raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname,
filename,
line))
return store

View File

@@ -72,7 +72,7 @@ class Command(BaseCommand):
arches = Arch.objects.filter(agnostic=False)
repos = Repo.objects.all()
arch_path_map = {arch: None for arch in arches}
arch_path_map = dict.fromkeys(arches)
all_paths = set()
total_paths = 0
for arch in arches:

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devel', '0010_merge_20230312_1527'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='social',
field=models.CharField(blank=True, max_length=200, null=True),
),
]

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import zoneinfo
from django.contrib.auth.models import Group, User
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
@@ -39,6 +40,9 @@ class UserProfile(models.Model):
website = models.URLField(max_length=200, null=True, blank=True)
website_rss = models.URLField(max_length=200, null=True, blank=True,
help_text='RSS Feed of your website for planet.archlinux.org')
social = models.URLField(max_length=200, null=True, blank=True,
verbose_name="Social account URL",
help_text="Mastodon or Fediverse account URL")
yob = models.IntegerField("Year of birth", null=True, blank=True,
validators=[MinValueValidator(1950), MaxValueValidator(2500)])
country = CountryField(blank=True)

View File

@@ -202,7 +202,8 @@ def non_existing_dependencies(packages):
def non_reproducible_packages(packages):
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname'))
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD,
pkg__pkgname__in=packages.values('pkgname'))
return linkify_non_reproducible_packages(statuses)
@@ -227,7 +228,7 @@ def orphan_dependencies(packages):
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
ORDER BY child.pkgname;
"""
""" # noqa: E501
cursor.execute(query)
for row in cursor.fetchall():

View File

@@ -216,7 +216,9 @@ def tier0_mirror_auth(request):
token = credentials[1]
groups = Group.objects.filter(name__in=SELECTED_GROUPS)
user = User.objects.filter(username=username, is_active=True, groups__in=groups).select_related('userprofile').first()
user = User.objects.filter(username=username,
is_active=True,
groups__in=groups).select_related('userprofile').first()
if not user:
return unauthorized

View File

@@ -11,6 +11,18 @@
"testing": true
}
},
{
"pk": 15,
"model": "main.repo",
"fields": {
"bugs_category": 33,
"staging": true,
"name": "Extra-Staging",
"bugs_project": 5,
"svn_root": "packages",
"testing": false
}
},
{
"pk": 1,
"model": "main.repo",
@@ -118,5 +130,17 @@
"svn_root": "packages",
"testing": true
}
},
{
"pk": 16,
"model": "main.repo",
"fields": {
"bugs_category": 10,
"staging": true,
"name": "Core-Staging",
"bugs_project": 1,
"svn_root": "packages",
"testing": false
}
}
]

View File

@@ -280,7 +280,8 @@ class Package(models.Model):
dep_pkgs = list(dep_pkgs)
dep = dep_pkgs[0]
if len(dep_pkgs) > 1:
dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
dep_pkgs = [d for d in dep_pkgs
if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
if len(dep_pkgs) > 0:
dep = dep_pkgs[0]
trimmed.append(dep)

View File

@@ -1,9 +1,10 @@
import cssmin
import jsmin
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from django.core.files.base import ContentFile
from django.utils.encoding import smart_str
import cssmin
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
"""

View File

@@ -9,24 +9,24 @@ register = template.Library()
@register.simple_tag
def jquery():
version = '3.6.0'
filename = 'jquery-%s.min.js' % version
filename = f'jquery-{version}.min.js'
link = staticfiles_storage.url(filename)
return mark_safe('<script type="text/javascript" src="%s"></script>' % link)
return mark_safe(f'<script type="text/javascript" src="{link}"></script>')
@register.simple_tag
def jquery_tablesorter():
version = '2.31.0'
filename = 'jquery.tablesorter-%s.min.js' % version
filename = f'jquery.tablesorter-{version}.min.js'
link = staticfiles_storage.url(filename)
return format_html('<script type="text/javascript" src="%s"></script>' % link)
return format_html('<script type="text/javascript" src="{link}"></script>', link=link)
@register.simple_tag
def d3js():
version = '3.5.0'
filename = 'd3-%s.min.js' % version
filename = f'd3-{version}.min.js'
link = staticfiles_storage.url(filename)
return format_html('<script type="text/javascript" src="%s"></script>' % link)
return format_html('<script type="text/javascript" src="{link}"></script>', link=link)
# vim: set ts=4 sw=4 et:

View File

@@ -8,8 +8,11 @@ register = template.Library()
def country_flag(country):
if not country:
return ''
return format_html('<span class="fam-flag fam-flag-%s" title="%s"></span> ' % (
str(country.code).lower(), str(country.name)))
return format_html(
'<span class="fam-flag fam-flag-{country_code}" title="{country_name}"></span> ',
country_code=str(country.code).lower(),
country_name=str(country.name),
)
# vim: set ts=4 sw=4 et:

View File

@@ -10,7 +10,7 @@ def format_key(key_id):
if len(key_id) in (8, 20):
return '0x%s' % key_id
elif len(key_id) == 40:
# normal display format is 5 groups of 4 hex chars seperated by spaces,
# normal display format is 5 groups of 4 hex chars separated by spaces,
# double space, then 5 more groups of 4 hex chars
split = tuple(key_id[i:i + 4] for i in range(0, 40, 4))
return '%s\u00a0 %s' % (' '.join(split[0:5]), ' '.join(split[5:10]))
@@ -35,7 +35,7 @@ def pgp_dev_key_link(key_id):
key_id = pad_key_id(key_id)
if not key_id:
return "Unknown"
link_text = (''.join((f'<span>{key_id[i:i+4]}</span>' for i in range(0, len(key_id), 4))))
link_text = (''.join((f'<span>{key_id[i:i + 4]}</span>' for i in range(0, len(key_id), 4))))
link_text = f'<div class="pgp-key-ids">{link_text}</div>'
return pgp_key_link(key_id, link_text)
@@ -51,11 +51,15 @@ def pgp_key_link(key_id, link_text=None):
return format_key(key_id)
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
scheme = 'https' if pgp_server_secure else 'http'
url = '%s://%s/pks/lookup?op=vindex&amp;fingerprint=on&amp;exact=on&amp;search=0x%s' % (scheme, pgp_server, key_id)
url = '%s://%s/pks/lookup?op=vindex&amp;fingerprint=on&amp;exact=on&amp;search=0x%s' % (scheme,
pgp_server,
key_id)
if link_text is None:
link_text = '0x%s' % key_id[-8:]
values = (url, format_key(key_id), link_text)
return format_html('<a href="%s" title="PGP key search for %s">%s</a>' % values)
return format_html('<a href="{url}" title="PGP key search for {key}">{content}</a>',
url=url,
key=format_key(key_id),
content=mark_safe(link_text))
@register.simple_tag

View File

@@ -184,12 +184,26 @@ def check_rsync_url(mirror_url, location, timeout):
with open(os.devnull, 'w') as devnull:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("rsync cmd: %s", ' '.join(rsync_cmd))
start = time.time()
proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE)
_, errdata = proc.communicate()
end = time.time()
log.duration = end - start
if proc.returncode != 0:
timeout_expired = False
# add an arbitrary 5-second buffer to ensure the process completes and to catch actual rsync timeouts.
rsync_subprocess_timeout = timeout + 5
try:
proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE)
_, errdata = proc.communicate(timeout=rsync_subprocess_timeout)
end = time.time()
log.duration = end - start
except subprocess.TimeoutExpired:
timeout_expired = True
proc.kill()
logger.debug("rsync command timeout error: %s, %s", url, errdata)
log.is_success = False
log.duration = None
log.error = f"rsync subprocess killed after {rsync_subprocess_timeout} seconds"
if proc.returncode != 0 and not timeout_expired:
logger.debug("error: %s, %s", url, errdata)
log.is_success = False
log.error = errdata.strip().decode('utf-8')
@@ -197,7 +211,7 @@ def check_rsync_url(mirror_url, location, timeout):
# don't record a duration as it is misleading
if proc.returncode in (1, 30, 35):
log.duration = None
else:
elif not timeout_expired:
logger.debug("success: %s, %.2f", url, log.duration)
if os.path.exists(lastsync_path):
with open(lastsync_path, 'r') as lastsync:

View File

@@ -27,7 +27,7 @@ def test_mirrorurl_get_full_url(mirrorurl):
def test_mirror_url_clean(mirrorurl):
mirrorurl.clean()
# TOOD(jelle): this expects HOSTNAME to resolve, maybe mock
# TODO(jelle): this expects HOSTNAME to resolve, maybe mock
assert mirrorurl.has_ipv4
# requires ipv6 on host... mock?
# assert mirrorurl.has_ipv6 == True

View File

@@ -54,10 +54,12 @@ class NewsCreateView(CreateView):
if settings.MAILMAN_PASSWORD:
headers['Approved'] = settings.MAILMAN_PASSWORD
template = loader.get_template('news/news_email_notification.txt')
author = newsitem.author.get_full_name()
from_ = f'"Arch Linux: Recent news updates: {author}" <{settings.ANNOUNCE_EMAIL}>'
EmailMessage(
subject=f'[arch-announce] {newsitem.title}',
body=template.render(ctx),
from_email=f'"Arch Linux: Recent news updates: {newsitem.author.get_full_name()}" <{settings.ANNOUNCE_EMAIL}>',
from_email=from_,
to=[settings.ANNOUNCE_EMAIL],
headers=headers).send()
return super(NewsCreateView, self).form_valid(form)

View File

@@ -52,8 +52,9 @@ def create_specification(package, log, finder):
return spec
def get_last_log(repo, pkgbase):
# Gitlab requires the path to the gitlab repo to be html encoded and project name encoded in a different special way
def get_tag_info(repo, pkgbase, version):
# Gitlab requires the path to the gitlab repo to be html encoded and
# project name encoded in a different special way
pkgrepo = urllib.parse.quote_plus(f'{settings.GITLAB_PACKAGE_REPO}/') + gitlab_project_name_to_path(pkgbase)
url = f'https://{settings.GITLAB_INSTANCE}/api/v4/projects/{pkgrepo}/repository/tags'
@@ -65,8 +66,12 @@ def get_last_log(repo, pkgbase):
return None
tags = r.json()
# filter out unrelated tags
tags = [tag for tag in tags if tag["name"] == version]
if len(tags) == 0:
logger.error("No tags found for pkgbase %s (%s)", pkgbase, repo)
logger.error("No tags found for pkgbase %s (%s) version %s", pkgbase, repo, version)
return None
tag = tags[0]
@@ -89,7 +94,7 @@ def add_signoff_comments():
if not group.default_spec:
continue
log = get_last_log(group.repo, group.pkgbase)
log = get_tag_info(group.repo, group.pkgbase, group.version)
if log is None:
continue
@@ -108,7 +113,8 @@ def cleanup_signoff_comments():
id_signoffs = [signoff.id for g in groups for signoff in g.signoffs]
logger.info("Keeping %s signoffs", len(id_signoffs))
# FakeSignoffSpecification's have no id
id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification, FakeSignoffSpecification)]
id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification,
FakeSignoffSpecification)]
logger.info("Keeping %s signoffspecifications", len(id_signoffspecs))
Signoff.objects.exclude(id__in=id_signoffs).delete()

View File

@@ -253,7 +253,8 @@ class UpdateManager(models.Manager):
if new_pkg:
update.action_flag = CHANGE
# ensure we should even be logging this
if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel and old_pkg.epoch == new_pkg.epoch:
if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel \
and old_pkg.epoch == new_pkg.epoch:
# all relevant fields were the same; e.g. a force update
return
else:
@@ -395,7 +396,8 @@ class RelatedToBase(models.Model):
# actually satisfy the requirements
if self.comparison and self.version:
alpm = AlpmAPI()
pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version, self.comparison, self.version)]
pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version,
self.comparison, self.version)]
if len(pkgs) == 0:
# couldn't find a package in the DB
# it should be a virtual depend (or a removed package)

View File

@@ -43,8 +43,8 @@ def pkg_details_link(pkg, link_title=None, honor_flagged=False):
link_content = link_title
if honor_flagged and pkg.flag_date:
link_content = '<span class="flagged">%s</span>' % link_title
link = '<a href="%s" title="View package details for %s">%s</a>'
return format_html(link % (pkg.get_absolute_url(), pkg.pkgname, link_content))
link = '<a href="{link}" title="View package details for {pkgname}">{content}</a>'
return format_html(link, link=pkg.get_absolute_url(), pkgname=pkg.pkgname, content=link_content)
# vim: set ts=4 sw=4 et:

View File

@@ -26,9 +26,9 @@ class RematchDeveloperTest(TransactionTestCase):
self.package.delete()
def test_basic(self):
with mock.patch('packages.management.commands.populate_signoffs.get_last_log') as get_last_log:
with mock.patch('packages.management.commands.populate_signoffs.get_tag_info') as get_tag_info:
comment = 'upgpkg: 0.1-1: rebuild'
get_last_log.return_value = {'message': f'{comment}\n', 'author': 'foo@archlinux.org'}
get_tag_info.return_value = {'message': f'{comment}\n', 'author': 'foo@archlinux.org'}
call_command('populate_signoffs')
signoff_spec = SignoffSpecification.objects.first()
@@ -36,8 +36,8 @@ class RematchDeveloperTest(TransactionTestCase):
assert signoff_spec.pkgbase == self.package.pkgbase
def test_invalid(self):
with mock.patch('packages.management.commands.populate_signoffs.get_last_log') as get_last_log:
get_last_log.return_value = None
with mock.patch('packages.management.commands.populate_signoffs.get_tag_info') as get_tag_info:
get_tag_info.return_value = None
call_command('populate_signoffs')
assert SignoffSpecification.objects.count() == 0

View File

@@ -67,6 +67,7 @@ def test_sort(client, package):
def test_packages(client, package):
response = client.get('/opensearch/packages/')
assert response.status_code == 200
assert 'template="http://example.com/opensearch/packages/"' in response.content.decode()
def test_packages_suggest(client, package):

View File

@@ -5,6 +5,7 @@ from collections import defaultdict
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.db.models import Q
from django.http import HttpResponse, HttpResponseBadRequest
@@ -21,10 +22,10 @@ from ..utils import get_wrong_permissions, multilib_differences
@require_safe
@cache_control(public=True, max_age=86400)
def opensearch(request):
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST'))
current_site = Site.objects.get_current()
return render(request, 'packages/opensearch.xml',
{'domain': domain},
{'domain': f'{request.scheme}://{current_site.domain}'},
content_type='application/opensearchdescription+xml')
@@ -140,8 +141,14 @@ def sonames(request):
name = request.GET.get('name')
if name:
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname', 'pkg__pkgver', 'pkg__pkgrel', 'pkg__epoch', 'pkg__repo__name')
packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'], 'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'], 'repo': soname['pkg__repo__name'].lower()} for soname in sonames]
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname',
'pkg__pkgver',
'pkg__pkgrel',
'pkg__epoch',
'pkg__repo__name')
packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'],
'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'],
'repo': soname['pkg__repo__name'].lower()} for soname in sonames]
else:
return HttpResponseBadRequest('name parameter is required')

View File

@@ -38,7 +38,8 @@ class FlagForm(forms.Form):
# make sure the message isn't garbage (only punctuation or whitespace)
# or spam (using a simple denylist)
# and ensure a certain minimum length
if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) or len(data) < 3:
if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) \
or len(data) < 3:
raise forms.ValidationError("Enter a valid and useful out-of-date message.")
return data

View File

@@ -50,7 +50,8 @@ class Migration(migrations.Migration):
('author', models.CharField(max_length=255)),
('publishdate', models.DateTimeField(db_index=True, verbose_name='publish date')),
('url', models.CharField(max_length=255, verbose_name='URL')),
('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='feed', to='planet.Feed')),
('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='feed', to='planet.Feed')),
],
options={
'verbose_name_plural': 'Feed Items',

View File

@@ -14,6 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='feeditem',
name='feed',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='planet.feed'),
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='items', to='planet.feed'),
),
]

View File

@@ -1,8 +1,9 @@
import json
from datetime import datetime
from datetime import datetime, timezone
from operator import attrgetter
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.db.models import Count, Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
@@ -25,12 +26,12 @@ def index(request):
else:
def updates():
return get_recent_updates()
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST'))
current_site = Site.objects.get_current()
context = {
'news_updates': News.objects.order_by('-postdate', '-id')[:15],
'pkg_updates': updates,
'staff_groups': StaffGroup.objects.all(),
'domain': domain,
'domain': f'{request.scheme}://{current_site.domain}',
}
return render(request, 'public/index.html', context)
@@ -92,12 +93,13 @@ def feeds(request):
@cache_control(max_age=307)
def keys(request):
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
users = User.objects.filter(
is_active=True, userprofile__id__in=profile_ids).order_by('first_name', 'last_name').select_related('userprofile')
users = User.objects.filter(is_active=True,
userprofile__id__in=profile_ids).order_by('first_name',
'last_name').select_related('userprofile')
user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users
if user.userprofile.pgp_key)
not_expired = Q(expires__gt=datetime.utcnow()) | Q(expires__isnull=True)
not_expired = Q(expires__gt=datetime.now(timezone.utc)) | Q(expires__isnull=True)
master_keys = MasterKey.objects.select_related('owner', 'revoker',
'owner__userprofile', 'revoker__userprofile').filter(
revoked__isnull=True)
@@ -153,7 +155,7 @@ def keys_json(request):
'group': 'master'
} for key in master_keys)
not_expired = Q(expires__gt=datetime.utcnow()) | Q(expires__isnull=True)
not_expired = Q(expires__gt=datetime.now(timezone.utc)) | Q(expires__isnull=True)
signatures = PGPSignature.objects.filter(not_expired, revoked__isnull=True)
edge_list = [{ 'signee': sig.signee, 'signer': sig.signer }
for sig in signatures]

View File

@@ -1,5 +1,5 @@
from base64 import b64encode
from datetime import datetime
from datetime import datetime, timezone
import pytest
from bencode import bencode
@@ -24,7 +24,7 @@ def torrent_data():
data = {
'comment': 'comment',
'created_by': 'Arch Linux',
'creation date': int(datetime.utcnow().timestamp()),
'creation date': int(datetime.now(timezone.utc).timestamp()),
'info': {
'name': 'arch.iso',
'length': 1,

View File

@@ -9,8 +9,10 @@ from .views import ReleaseDetailView, ReleaseListView
releases_patterns = [
path('', ReleaseListView.as_view(), name='releng-release-list'),
path('json/', views.releases_json, name='releng-release-list-json'),
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()), name='releng-release-detail'),
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), name='releng-release-torrent'),
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()),
name='releng-release-detail'),
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent),
name='releng-release-torrent'),
]
netboot_patterns = [

View File

@@ -1,19 +1,19 @@
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
Django==5.0.8
Django==5.1.13
IPy==1.1
Markdown==3.3.7
bencode.py==4.0.0
django-countries==7.6.1
django-extensions==3.2.3
django-extensions==4.1
jsmin==3.0.1
pgpdump==1.5
parse==1.19.0
parse==1.20.2
sqlparse==0.5.0
django-csp==3.7
django-csp==4.0
ptpython==2.0.4
feedparser==6.0.10
feedparser==6.0.11
bleach==6.0.0
requests==2.32.0
xtarfile==0.1.0
zstandard==0.17.0
requests==2.32.4
xtarfile==0.2.1
zstandard==0.23.0
django-prometheus==2.3.1

View File

@@ -24,7 +24,6 @@ select = [
]
ignore = [
"E501", # line lengt violation
"E731", # Do not assign a `lambda` expression, use a `def`
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
@@ -34,3 +33,8 @@ ignore = [
"DJ008", # Model does not define `__str__` method
"DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
]
exclude = [
"*/migrations/*.py", # Ignore Django migrations
"src/cssmin/*" # cssmin, not our code
]

View File

@@ -41,9 +41,6 @@ SITE_ID = 1
DATE_FORMAT = 'Y-m-d'
DATETIME_FORMAT = 'Y-m-d H:i'
# Disable so our own DATE_FORMAT/DATETIME_FORMAT is used.
USE_L10N = False
# Login URL configuration
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
@@ -269,8 +266,16 @@ if DEBUG_TOOLBAR:
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
if PROMETHEUS_METRICS:
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', *list(MIDDLEWARE), 'django_prometheus.middleware.PrometheusAfterMiddleware']
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware',
*list(MIDDLEWARE),
'django_prometheus.middleware.PrometheusAfterMiddleware']
INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
# Assume all URLField will be HTTPS if not specified.
# NOTE: this can be removed once we bump Django to 6.x
# where `https` becomes the default.
FORMS_URLFIELD_ASSUME_HTTPS = True
# vim: set ts=4 sw=4 et:

View File

@@ -754,6 +754,10 @@ table.results {
text-align: center;
}
.results [hidden] {
display: none;
}
/* pkglist: layout */
#pkglist-about {
margin-top: 1.5em;
@@ -1056,12 +1060,12 @@ table td.country {
background: #ffd;
}
.results tr:nth-child(even),
.results tr:nth-child(even of :not([hidden])),
#article-list tr:nth-child(even) {
background: #e4eeff;
}
.results tr:nth-child(odd),
.results tr:nth-child(odd of :not([hidden])),
#article-list tr:nth-child(odd) {
background: #fff;
}
@@ -1203,12 +1207,12 @@ ul.signoff-list {
/* style input as a normal anchor */
input {
background: none!important;
background: none !important;
border: none;
padding: 0!important;
/*optional*/
padding: 0 0.5em !important;
/* optional */
font-family: arial, sans-serif;
font-size: 0.9em;
font-size: 100%;
/*input has OS specific font-family*/
color: #07b;
}

View File

@@ -212,8 +212,14 @@ function filter_pkgs_list(filter_ele, tbody_ele) {
rows = rows.has('.incomplete');
}
/* hide all rows, then show the set we care about */
all_rows.hide();
rows.show();
// note that we don't use .hide() from jQuery because it adds display:none
// which is very expensive to query in CSS ([style*="display: none"])
all_rows.each(function() {
$(this).attr('hidden', true);
});
rows.each(function() {
$(this).removeAttr('hidden');
});
$('#filter-count').text(rows.length);
/* make sure we update the odd/even styling from sorting */
$('.results').trigger('applyWidgets', [false]);
@@ -318,6 +324,10 @@ function filter_signoffs() {
/* start with all rows, and then remove ones we shouldn't show */
var rows = $('#tbody_signoffs').children(),
all_rows = rows;
/* apply the filters, cheaper ones first */
if ($('#id_mine_only').is(':checked')) {
rows = rows.filter('.mine');
}
/* apply arch and repo filters */
$('#signoffs_filter .arch_filter').add(
'#signoffs_filter .repo_filter').each(function() {
@@ -330,8 +340,14 @@ function filter_signoffs() {
rows = rows.has('td.signoff-no');
}
/* hide all rows, then show the set we care about */
all_rows.hide();
rows.show();
// note that we don't use .hide() from jQuery because it adds display:none
// which is very expensive to query in CSS ([style*="display: none"])
all_rows.each(function() {
$(this).attr('hidden', true);
});
rows.each(function() {
$(this).removeAttr('hidden');
});
$('#filter-count').text(rows.length);
/* make sure we update the odd/even styling from sorting */
$('.results').trigger('applyWidgets', [false]);
@@ -340,6 +356,7 @@ function filter_signoffs() {
function filter_signoffs_reset() {
$('#signoffs_filter .arch_filter').prop('checked', true);
$('#signoffs_filter .repo_filter').prop('checked', true);
$('#id_mine_only').prop('checked', false);
$('#id_pending').prop('checked', false);
filter_signoffs();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -30,7 +30,7 @@
<tbody>
{% for entry in admin_log %}
<tr>
<th scope="row">{{ entry.action_time|date:"DATETIME_FORMAT" }}</th>
<th scope="row">{{ entry.action_time|date:"Y-m-d H:i" }}</th>
{% if log_user %}
<td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td>
{% else %}

View File

@@ -36,8 +36,8 @@
{% endif %}{% endwith %}</td>
<td>{{ pkg.repo.name }}</td>
<td>{{ pkg.arch.name }}</td>
<td>{{ pkg.flag_date|date }}</td>
<td>{{ pkg.last_update|date }}</td>
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr class="empty"><td colspan="7"><em>No flagged packages to display</em></td></tr>
@@ -68,7 +68,7 @@
<td>{{ group.version }}</td>
<td>{{ group.arch.name }}</td>
<td>{{ group.target_repo }}</td>
<td>{{ group.last_update|date }}</td>
<td>{{ group.last_update|date:"Y-m-d" }}</td>
{% if group.specification.known_bad %}
<td class="approval signoff-bad">Bad</td>
{% else %}
@@ -138,7 +138,7 @@
<tr>
<td class="wrap"><a href="{{ todo.get_absolute_url }}"
title="View todo list: {{ todo.name }}">{{ todo.name }}</a></td>
<td>{{ todo.created|date }}</td>
<td>{{ todo.created|date:"Y-m-d" }}</td>
<td>{{ todo.creator.get_full_name }}</td>
<td>{{ todo.pkg_count }}</td>
<td>{{ todo.incomplete_count }}</td>

View File

@@ -60,9 +60,9 @@
{% else %}
<td>{{ pkg.full_version }}</td>
{% endif %}
<td>{{ pkg.last_update|date }}</td>
<td>{{ pkg.build_date|date }}</td>
<td>{{ pkg.flag_date|date }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
<td>{{ pkg.build_date|date:"Y-m-d" }}</td>
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
{% for attr in column_attrs %}
<td>{{ pkg|attribute:attr }}</td>
{% endfor %}

View File

@@ -7,7 +7,7 @@
<div class="box">
<h2>Tier 0 Mirror usage information</h2>
<p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with a HTTP Basic Auth password unique per Staff member.</p>
<p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with an HTTP Basic Auth password unique per Staff member.</p>
{% if mirror_url %}
<code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button>

View File

@@ -33,7 +33,7 @@
<tbody>
{% for item in news_list %}
<tr>
<td>{{ item.postdate|date }}</td>
<td>{{ item.postdate|date:"Y-m-d" }}</td>
<td class="wrap"><a href="{{ item.get_absolute_url }}"
title="View: {{ item.title }}">{{ item.title }}</a></td>
<td>{{ item.author.get_full_name }}</td>

View File

@@ -25,7 +25,7 @@
</ul>
{% endif %}
<p class="article-info">{{ news.postdate|date }} - {{ news.author.get_full_name }}</p>
<p class="article-info">{{ news.postdate|date:"Y-m-d" }} - {{ news.author.get_full_name }}</p>
<div class="article-content" itemprop="articleBody">{{ news.html }}</div>
</div>

View File

@@ -16,7 +16,7 @@
<th>Multilib Version</th>
<th>x86_64 Version</th>
<th>x86_64 Name</th>
<th>x864_ Repo</th>
<th>x86_64 Repo</th>
<th>Multilib Last Updated</th>
<th>x86_64 Last Updated</th>
</tr>
@@ -29,8 +29,8 @@
<td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td>
<td>{% pkg_details_link pkg2 %}</td>
<td>{{ pkg2.repo }}</td>
<td>{{ pkg1.last_update|date }}</td>
<td>{{ pkg2.last_update|date }}</td>
<td>{{ pkg1.last_update|date:"Y-m-d" }}</td>
<td>{{ pkg2.last_update|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load package_extras %}
{% load humanize %}
{% load details_link %}
{% block title %}Arch Linux - Flag Package - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %}
{% block head %}<meta name="robots" content="noindex"/>{% endblock %}
@@ -29,9 +30,9 @@
with your additional text.</p>
<p><strong>Note:</strong> Do <em>not</em> use this facility if the
package is broken! The package will be unflagged and the report will be ignored!
<a href="https://bugs.archlinux.org/" title="Arch Linux Bugtracker">Use the
bugtracker to file a bug</a> instead.</p>
package is broken! The package will be unflagged and the report will be ignored! File an issue on
<a href="{% bugs_list package %}" title="Bug tickets for {{ package }}">the package's GitLab repository</a>
instead.</p>
<p>Please confirm your flag request for {{package.pkgname}}:</p>

View File

@@ -23,7 +23,7 @@
<td><a href="/groups/{{ grp.arch }}/{{ grp.name }}/"
title="Group details for {{ grp.name }}">{{ grp.name }}</a></td>
<td>{{ grp.count }}</td>
<td>{{ grp.last_update|date }}</td>
<td>{{ grp.last_update|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -27,7 +27,7 @@
</li>
{% endif %}
{% if pkg.flag_date %}
<li><span class="flagged">Flagged out-of-date on {{ pkg.flag_date|date }}</span></li>
<li><span class="flagged">Flagged out-of-date on {{ pkg.flag_date|date:"Y-m-d" }}</span></li>
{% with tp=pkg.in_testing %}{% if tp %}
<li><span class="flagged">Version
<a href="{{ tp.get_absolute_url }}"
@@ -182,19 +182,19 @@
{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
</tr><tr>
<th>Build Date:</th>
<td>{{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC</td>
<td>{{ pkg.build_date|date:"Y-m-d H:i" }} UTC</td>
</tr>{% if pkg.signature %}<tr>
<th>Signed By:</th>
<td>{% with signer=pkg.signer %}{% if signer %}{% pgp_key_link pkg.signature.key_id signer.get_full_name|safe %}{% else %}Unknown ({% pgp_key_link pkg.signature.key_id|safe %}){% endif %}{% endwith %}</td>
</tr><tr>
<th>Signature Date:</th>
<td>{{ pkg.signature.creation_time|date:"DATETIME_FORMAT" }} UTC</td>
<td>{{ pkg.signature.creation_time|date:"Y-m-d H:i" }} UTC</td>
</tr>{% else %}<tr>
<th>Signed By:</th>
<td>Unsigned</td>
</tr>{% endif %}<tr>
<th>Last Updated:</th>
<td>{{ pkg.last_update|date:"DATETIME_FORMAT" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td>
<td>{{ pkg.last_update|date:"Y-m-d H:i" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td>
</tr>
{% if user.is_authenticated %}<tr>
<th>Reproducible Status:</th>
@@ -207,7 +207,7 @@
{% endif %}
{% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr>
<th>Last Flag Request:</th>
<td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date }}:<br/>
<td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date:"Y-m-d" }}:<br/>
<div class="userdata">{{ flag_request.message|linebreaksbr|default:"{no message}" }}</div></td>
</tr>{% endif %}{% endwith %}{% endif %}
</table>

View File

@@ -33,8 +33,8 @@
<td>{{ pkg.full_version }}</td>
{% endif %}
<td class="wrap">{{ pkg.pkgdesc }}</td>
<td>{{ pkg.last_update|date }}</td>
<td>{{ pkg.flag_date|date }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -68,8 +68,8 @@
<td>{{ pkg.full_version }}</td>
{% endif %}
<td class="wrap">{{ pkg.pkgdesc }}</td>
<td>{{ pkg.last_update|date }}</td>
<td>{{ pkg.flag_date|date }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
</tbody>
@@ -115,8 +115,8 @@
<td>{{ pkg.full_version }}</td>
{% endif %}
<td class="wrap">{{ pkg.pkgdesc }}</td>
<td>{{ pkg.last_update|date }}</td>
<td>{{ pkg.flag_date|date }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
</tr>
{% empty %}
<tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr>

View File

@@ -26,6 +26,8 @@
<div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label>
<input type="checkbox" name="repo_{{ repo_name|lower }}" id="id_repo_{{ repo_name|lower }}" class="repo_filter" value="{{ repo_name|lower }}" checked="checked"/></div>
{% endfor %}
<div><label for="id_mine_only" title="Show only packages packaged by me">Only Mine</label>
<input type="checkbox" name="mine_only" id="id_mine_only" value="mine_only"/></div>
<div><label for="id_pending" title="Packages with not enough signoffs">Only Pending Approval</label>
<input type="checkbox" name="pending" id="id_pending" value="pending"/></div>
<div><label>&nbsp;</label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div>
@@ -50,13 +52,14 @@
</tr>
</thead>
<tbody id="tbody_signoffs">
{% for group in signoff_groups %}<tr class="{{ group.arch.name }} {{ group.target_repo|lower }}">
{% for group in signoff_groups %}
<tr class="{% if user == group.packager %} mine{% endif %} {{ group.arch.name }} {{ group.target_repo|lower }}">
<td>{% pkg_details_link group.package %} {{ group.version }}</td>
<td>{{ group.arch.name }}</td>
<td>{{ group.target_repo }}</td>
<td>{{ group.packager|default:"Unknown" }}</td>
<td>{{ group.packages|length }}</td>
<td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date }}</td>
<td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date:"Y-m-d" }}</td>
{% if group.specification.known_bad %}
<td class="approval signoff-bad">Bad</td>
{% else %}

View File

@@ -24,7 +24,7 @@
<a href="{{ entry.url }}"
title="View full article: {{ entry.title }}">{{ entry.title }}</a>
</h4>
<p class="timestamp">{{ entry.publishdate|date }}</p>
<p class="timestamp">{{ entry.publishdate|date:"Y-m-d" }}</p>
<div class="article-content">
{{ entry.summary |safe }}
</div>

View File

@@ -49,6 +49,11 @@
<td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}"
title="Visit the website for {{ dev.get_full_name }}">
{{ prof.website }}</a>{% endif %}</td>
</tr><tr>
<th>Social:</th>
<td>{% if prof.social %}<a itemprop="url" href="{{ prof.social }}"
title="Visit social account for {{ dev.get_full_name }}" rel="me">
{{ prof.social }}</a>{% endif %}</td>
</tr><tr>
<th>Occupation:</th>
<td>{{ prof.occupation }}</td>

View File

@@ -53,37 +53,10 @@
<img src="{% static "nitrokey_logo.png" %}"
class="sponsor-btn-nitrokey" title="" alt="Nitrokey logo"/></a>
<p>We would also like to thank <a href="https://www.privateinternetaccess.com/"
title="Private Internet Access">Private Internet Access</a> for sponsoring
dedicated servers across the globe. Private Internet Access is the leading
VPN Service provider specializing in secure, encrypted VPN tunnels which
create several layers of privacy and security providing users safety on the
internet.</p>
<a href="https://www.privateinternetaccess.com/" title="Private Internet Access">
<img src="{% static "pia_logo.png" %}"
class="sponsor-btn-pia" title="" alt="Private Internet Access logo"/></a>
<p>We would also like to thank <a href="https://www.shells.com/"
title="Shells">Shells.com</a> for their monetary donation.
Shells provides you with a 1-click, powerful virtual desktop environment,
driven by a cloud computer, without leaving your browser! It's your
personal workspace in the cloud.</p>
<a href="https://www.shells.com/" title="Shells">
<img src="{% static "shells_logo.png" %}"
title="" alt="Shells logo"/></a>
<p>We would also like to thank <a href="https://uptimerobot.com/"
title="UptimeRobot">UptimeRobot</a> for providing their monitoring service to us.
UptimeRobot is a leading uptime monitoring service.</p>
<a href="https://uptimerobot.com/" title="UptimeRobot">
<img src="{% static "uptimerobot_logo.png" %}"
title="" alt="UptimeRobot logo"/></a>
<h3>Past donors</h3>
<p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> &amp; <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p>
<div id="donor-list">
<ul>
{% for donor in donors %}

View File

@@ -25,6 +25,8 @@
It is intended for new installations only; an existing Arch Linux system
can always be updated with <code>pacman -Syu</code>.</p>
<p>Images for installing Arch can be downloaded via <a href="#bittorrent-download">BitTorrent</a> or right here in your browser from one of the <a href="#http-downloads">Arch HTTP(S) mirrors down below</a>.</p>
<ul>
{% if release.version %}<li><strong>Current Release:</strong> {{ release.version }}</li>{% endif %}
{% if release.kernel_version %}<li><strong>Included Kernel:</strong> {{ release.kernel_version }}</li>{% endif %}
@@ -52,7 +54,7 @@
to update your existing system. You may be looking for
<a href="{% url 'mirrorlist' %}">an updated mirrorlist</a> instead.</p>
<h3>BitTorrent Download (recommended)</h3>
<h3 id="bittorrent-download">BitTorrent Download (recommended)</h3>
<p>If you can spare the bytes, please leave the client open after your
download is finished, so you can seed it back to others.
@@ -75,13 +77,6 @@
title="Arch Linux Netboot">Arch Linux Netboot</a></li>
</ul>
<h3>Vagrant images</h3>
<p>Vagrant images for libvirt and virtualbox are available on the <a href="https://app.vagrantup.com/archlinux/boxes/archlinux">Vagrant Cloud</a>. You can bootstrap the image with the following commands:</p>
<code>vagrant init archlinux/archlinux</code>
<br/>
<code>vagrant up</code>
<h3>Docker image</h3>
<p>The official Docker image is available on <a href="https://hub.docker.com/_/archlinux/">Docker Hub</a>. You can run the image with the following command:</p>
@@ -91,10 +86,18 @@
<p>Official virtual machine images are available for download on our <a href="https://gitlab.archlinux.org/archlinux/arch-boxes/-/packages">GitLab instance</a>, more information is available in the <a href="https://gitlab.archlinux.org/archlinux/arch-boxes/">README</a>.</p>
<h3>HTTP Direct Downloads</h3>
<h3>WSL images</h3>
<p>The official WSL image can be installed with the following command (in a PowerShell prompt from a Windows system with WSL 2 installed):</p>
<code>wsl --install archlinux</code>
<p>It is also available for download on <a href="https://geo.mirror.pkgbuild.com/wsl/latest">mirrors</a>.</p>
<p>More information available in the <a href="https://wiki.archlinux.org/title/Install_Arch_Linux_on_WSL">Wiki</a>.</p>
<h3 id="http-downloads">HTTP Direct Downloads</h3>
<p>In addition to the BitTorrent links above, install images can also be
downloaded via HTTP from the mirror sites listed below. Please
downloaded via HTTP from the <a href="#download-mirrors">mirror sites listed below</a>. Please
ensure the download image matches the checksum from the <code>sha256sums.txt</code> or <code>b2sums.txt</code> file linked below.</p>
<h4 id="checksums">Checksums and signatures</h4>
@@ -130,15 +133,15 @@
<pre><code>$ b2sum -c b2sums.txt</code></pre>
To verify the PGP signature using Sequoia, first download the release signing key from WKD:
<pre><code>$ sq network wkd fetch {{ release.wkd_email }} -o release-key.pgp</code></pre>
<pre><code>$ sq network wkd search {{ release.wkd_email }} --output release-key.pgp</code></pre>
With this signing key, verify the signature:
<pre><code>$ sq verify --signer-file release-key.pgp --detached archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
<pre><code>$ sq verify --signer-file release-key.pgp --signature-file archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
Alternatively, using GnuPG, download the signing key from WKD:
<pre><code>$ gpg --auto-key-locate clear,wkd -v --locate-external-key {{ release.wkd_email }}</code></pre>
Verify the signature:
<pre><code>$ gpg --keyserver-options auto-key-retrieve --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
<pre><code>$ gpg --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
{% cache 600 download-mirrors %}
<div id="download-mirrors">

View File

@@ -50,7 +50,7 @@
<a href="{{ news.get_absolute_url }}"
title="View full article: {{ news.title }}">{{ news.title }}</a>
</h4>
<p class="timestamp">{{ news.postdate|date }}</p>
<p class="timestamp">{{ news.postdate|date:"Y-m-d" }}</p>
<div class="article-content">
{% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }}
{% else %}{{ news.html|truncatewords_html:100 }}{% endif %}
@@ -63,7 +63,7 @@
</h3>
<dl class="newslist">
{% endif %}
<dt>{{ news.postdate|date }}</dt>
<dt>{{ news.postdate|date:"Y-m-d" }}</dt>
<dd>
<a href="{{ news.get_absolute_url }}"
title="View full article: {{ news.title }}">{{ news.title }}</a>
@@ -133,8 +133,6 @@
<h4>Support</h4>
<ul>
<li><a href="{% url 'page-donate' %}" title="Help support Arch Linux">Donate</a></li>
<li><a href="https://www.unixstickers.com/tag/archlinux" title="Arch
Linux stickers, t-shirts, hoodies, mugs, posters and pins">Products via Unixstickers</a></li>
<li><a href="https://www.freewear.org/?page=list_items&amp;org=Archlinux"
title="T-shirts">T-shirts via Freewear</a></li>
<li><a href="https://www.hellotux.com/arch"
@@ -155,6 +153,7 @@
<ul>
<li><a href="https://wiki.archlinux.org/title/Getting_involved"
title="Getting involved">Getting involved</a></li>
<li><a href="https://devblog.archlinux.page" title="Dev Blog">Dev Blog</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/"
title="Official Arch projects (git)">Projects in Git</a></li>
<li><a href="https://wiki.archlinux.org/title/DeveloperWiki"
@@ -202,20 +201,10 @@
title="" alt="Hetzner logo"/>
</a>
<a href="https://www.privateinternetaccess.com/" title="Private Internet Access">
<img src="{% static "pia_button.png" %}"
title="" alt="Private Internet Access logo"/>
</a>
<a href="https://icons8.com/" title="Icons8">
<img src="{% static "icons8_logo.png" %}"
title="" alt="Icons8 logo"/>
</a>
<a href="https://www.shells.com" title="Shells.com">
<img src="{% static "shells_logo.png" %}"
title="" alt="Shells logo"/>
</a>
</div>
{% endcache %}
{% endblock %}

View File

@@ -9,7 +9,7 @@
<h2>{{ release.version }}</h2>
<ul>
<li><strong>Release Date:</strong> {{ release.release_date|date }}</li>
<li><strong>Release Date:</strong> {{ release.release_date|date:"Y-m-d" }}</li>
{% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %}
<li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li>
{% if release.torrent_data %}
@@ -38,7 +38,7 @@
<ul>
<li><strong>Comment:</strong> {{ torrent.comment }}</li>
<li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"DATETIME_FORMAT" }} UTC</li>
<li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"Y-m-d H:i" }} UTC</li>
<li><strong>Created By:</strong> {{ torrent.created_by }}</li>
<li><strong>Announce URL:</strong> {{ torrent.announce }}</li>
<li><strong>File Name:</strong> {{ torrent.file_name }}</li>

View File

@@ -42,7 +42,7 @@
<a href="{{ item.magnet_uri }}"
title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a>
{% endif %}</td>
<td>{{ item.release_date|date }}</td>
<td>{{ item.release_date|date:"Y-m-d" }}</td>
<td><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td>
<td>{{ item.kernel_version|default:"" }}</td>
<td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td>

View File

@@ -37,7 +37,7 @@
<tr>
<td class="wrap"><a href="{{ list.get_absolute_url }}"
title="View todo list: {{ list.name }}">{{ list.name }}</a></td>
<td>{{ list.created|date }}</td>
<td>{{ list.created|date:"Y-m-d" }}</td>
<td>{{ list.creator.get_full_name }}</td>
<td>{{ list.pkg_count }}</td>
<td>{{ list.incomplete_count }}</td>

View File

@@ -2,6 +2,8 @@
{% load static %}
{% load package_extras %}
{% load todolists %}
{% load tz %}
{% load humanize %}
{% block title %}Arch Linux - Todo: {{ list.name }}{% endblock %}
@@ -23,7 +25,7 @@
{% endif %}
</ul>
<div class="todo-info">{{ list.created|date }} - {{ list.creator.get_full_name }}</div>
<div class="todo-info">{{ list.created|date:"Y-m-d" }} - {{ list.creator.get_full_name }}</div>
<div class="todo-description">
{{list.stripped_description|default:'(no description)'|urlize|linebreaks}}
@@ -103,7 +105,15 @@
<span class="{{ pkg.status_css_class }}">{{ pkg.get_status_display }}</span>
{% endif %}
</td>
<td>{{ pkg.user|default:"" }}</td>
<td>
{% if pkg.user %}
{% if user.is_authenticated %}
{{ pkg.user }} <span title="{{ pkg.last_modified|timezone:user.userprofile.time_zone|date:"Y-m-d H:i T" }}">({{ pkg.last_modified|naturaltime }})</span>
{% else %}
{{ pkg.user }} <span title="{{ pkg.last_modified|date:"Y-m-d H:i T" }}">({{ pkg.last_modified|naturaltime }})</span>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -28,7 +28,9 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(db_index=True)),
('last_modified', models.DateTimeField(editable=False)),
('raw', models.TextField(blank=True)),
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_todolists', to=settings.AUTH_USER_MODEL)),
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='created_todolists',
to=settings.AUTH_USER_MODEL)),
],
options={
'get_latest_by': 'created',
@@ -43,13 +45,16 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(editable=False)),
('last_modified', models.DateTimeField(editable=False)),
('removed', models.DateTimeField(blank=True, null=True)),
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')], default=0)),
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')],
default=0)),
('comments', models.TextField(blank=True, null=True)),
('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')),
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Package')),
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
to='main.Package')),
('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Repo')),
('todolist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todolists.Todolist')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL)),
],
options={
'get_latest_by': 'created',

View File

@@ -13,6 +13,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='todolist',
name='kind',
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0, help_text='(Rebuild for soname bumps, Task for independent tasks)'),
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0,
help_text='(Rebuild for soname bumps, Task for independent tasks)'),
),
]

View File

@@ -23,7 +23,8 @@ class Todolist(models.Model):
description = models.TextField()
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
created = models.DateTimeField(db_index=True)
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES, help_text='(Rebuild for soname bumps, Task for independent tasks)')
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES,
help_text='(Rebuild for soname bumps, Task for independent tasks)')
last_modified = models.DateTimeField(editable=False)
raw = models.TextField(blank=True)

View File

@@ -13,8 +13,9 @@ def todopkg_details_link(todopkg):
pkg = todopkg.pkg
if not pkg:
return todopkg.pkgname
link = '<a href="%s" title="View package details for %s">%s</a>'
link = '<a href="{url}" title="View package details for {pkgname}">{pkgname}</a>'
url = pkg_absolute_url(todopkg.repo, todopkg.arch, pkg.pkgname)
return format_html(link % (url, pkg.pkgname, pkg.pkgname))
return format_html(link, url=url, pkgname=pkg.pkgname)
# vim: set ts=4 sw=4 et: