Compare commits
68 Commits
43c333727d
...
v25.9.15
Author | SHA1 | Date | |
---|---|---|---|
e024b881a5 | |||
![]() |
fb9b330535 | ||
![]() |
6bc1b49071 | ||
![]() |
0cf24055d5 | ||
52e9a1ae0e | |||
b386d89d6c
|
|||
![]() |
59229f280b | ||
![]() |
4c57725862 | ||
![]() |
8ded12a7cf | ||
![]() |
0180242d2a | ||
![]() |
bf29716008 | ||
9e36ad4e38 | |||
![]() |
2027adaaba | ||
![]() |
05abc7f386 | ||
![]() |
c1d70301c9 | ||
![]() |
323002ffe1 | ||
![]() |
a49a2cadc5 | ||
![]() |
a733efa173 | ||
![]() |
ce776a6297 | ||
![]() |
7126b7b006 | ||
![]() |
ba4eb27ae3 | ||
![]() |
375713717a | ||
93e59ab09b | |||
![]() |
5e33be47a7 | ||
![]() |
4473a9cdba | ||
49d616ef32 | |||
12f795bcf8 | |||
![]() |
24b21bffed | ||
![]() |
7f5d7e9082 | ||
![]() |
653b482ec5 | ||
![]() |
ecc4bdf0a7 | ||
![]() |
a45b88da4b | ||
![]() |
e460ba4727 | ||
![]() |
910e428baa | ||
8e6bc69713 | |||
![]() |
f624f5677b | ||
![]() |
2064099696 | ||
![]() |
67209075c5 | ||
![]() |
336d686ca2 | ||
![]() |
4f0e24f1f7 | ||
7bf65b63ff
|
|||
188ead820d | |||
![]() |
e07054c8ea | ||
![]() |
97aae09dce | ||
![]() |
0ce1a0ea5f | ||
![]() |
f38770be76 | ||
![]() |
2d39dc6379 | ||
![]() |
df4b0bfd67 | ||
![]() |
5da7fa80c5 | ||
![]() |
8d495d4fa7 | ||
![]() |
796c3f410f | ||
![]() |
37687bf9e4 | ||
7accacd5fd | |||
10fecc63e9
|
|||
368e248efc | |||
a71b1f783e
|
|||
77531b1948
|
|||
![]() |
2bf2fa235f | ||
![]() |
87a0d37953 | ||
![]() |
4210a46f9a | ||
![]() |
bd4d50b84e | ||
![]() |
fcd473608c | ||
74bfaed558 | |||
![]() |
4e25b014e4 | ||
1a1d963ca4
|
|||
f8d5473c25 | |||
![]() |
c15b22203a | ||
![]() |
b39c4c9385 |
2
.flake8
2
.flake8
@@ -1,3 +1,3 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 300
|
max-line-length = 118
|
||||||
ignore = E731, E241, E741
|
ignore = E731, E241, E741
|
||||||
|
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
REGISTRY: gitea.artixlinux.org
|
REGISTRY: gitea.artixlinux.org
|
||||||
DH_REGISTRY: docker.io
|
DH_REGISTRY: docker.io
|
||||||
REPO_ORG: ${{ gitea.repository_owner }}
|
REPO_ORG: ${{ gitea.repository_owner }}
|
||||||
DH_ORG: corysanin
|
DH_ORG: artixlinux
|
||||||
IMAGE_NAME: archweb
|
IMAGE_NAME: archweb
|
||||||
DH_IMAGE_NAME: archweb
|
DH_IMAGE_NAME: archweb
|
||||||
ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }}
|
ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }}
|
||||||
|
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
@@ -9,14 +9,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python 3.11
|
- name: Set up Python 3.13
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.13
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
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
|
- name: Lint with flake8
|
||||||
run: |
|
run: |
|
||||||
make lint
|
make lint
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.12-alpine3.20 AS base
|
FROM python:3.13-alpine3.20 AS base
|
||||||
|
|
||||||
RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
|
RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
|
||||||
|
|
||||||
|
@@ -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.
|
* 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
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.
|
||||||
|
@@ -71,7 +71,8 @@ class Database(object):
|
|||||||
retry = False
|
retry = False
|
||||||
except OperationalError as exc:
|
except OperationalError as exc:
|
||||||
retry_count += 1
|
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)
|
time.sleep(5)
|
||||||
|
|
||||||
if retry_count == self.retry_limit:
|
if retry_count == self.retry_limit:
|
||||||
|
@@ -59,7 +59,7 @@ class Command(BaseCommand):
|
|||||||
arches = Arch.objects.filter(agnostic=False)
|
arches = Arch.objects.filter(agnostic=False)
|
||||||
repos = Repo.objects.all()
|
repos = Repo.objects.all()
|
||||||
|
|
||||||
arch_path_map = {arch: None for arch in arches}
|
arch_path_map = dict.fromkeys(arches)
|
||||||
all_paths = set()
|
all_paths = set()
|
||||||
total_paths = 0
|
total_paths = 0
|
||||||
for arch in arches:
|
for arch in arches:
|
||||||
@@ -89,7 +89,9 @@ class Command(BaseCommand):
|
|||||||
for name in all_paths:
|
for name in all_paths:
|
||||||
manager.add_watch(name, mask)
|
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)
|
return pyinotify.Notifier(manager, handler)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -539,7 +539,9 @@ def parse_info(pkgname, filename, iofile):
|
|||||||
elif blockname:
|
elif blockname:
|
||||||
store[blockname].append(line)
|
store[blockname].append(line)
|
||||||
else:
|
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
|
return store
|
||||||
|
|
||||||
|
|
||||||
|
@@ -72,7 +72,7 @@ class Command(BaseCommand):
|
|||||||
arches = Arch.objects.filter(agnostic=False)
|
arches = Arch.objects.filter(agnostic=False)
|
||||||
repos = Repo.objects.all()
|
repos = Repo.objects.all()
|
||||||
|
|
||||||
arch_path_map = {arch: None for arch in arches}
|
arch_path_map = dict.fromkeys(arches)
|
||||||
all_paths = set()
|
all_paths = set()
|
||||||
total_paths = 0
|
total_paths = 0
|
||||||
for arch in arches:
|
for arch in arches:
|
||||||
|
16
devel/migrations/0011_userprofile_social.py
Normal file
16
devel/migrations/0011_userprofile_social.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -39,6 +40,9 @@ class UserProfile(models.Model):
|
|||||||
website = models.URLField(max_length=200, null=True, blank=True)
|
website = models.URLField(max_length=200, null=True, blank=True)
|
||||||
website_rss = 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')
|
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,
|
yob = models.IntegerField("Year of birth", null=True, blank=True,
|
||||||
validators=[MinValueValidator(1950), MaxValueValidator(2500)])
|
validators=[MinValueValidator(1950), MaxValueValidator(2500)])
|
||||||
country = CountryField(blank=True)
|
country = CountryField(blank=True)
|
||||||
|
@@ -202,7 +202,8 @@ def non_existing_dependencies(packages):
|
|||||||
|
|
||||||
|
|
||||||
def non_reproducible_packages(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)
|
return linkify_non_reproducible_packages(statuses)
|
||||||
|
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ def orphan_dependencies(packages):
|
|||||||
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
|
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
|
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;
|
ORDER BY child.pkgname;
|
||||||
"""
|
""" # noqa: E501
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
|
|
||||||
for row in cursor.fetchall():
|
for row in cursor.fetchall():
|
||||||
|
@@ -216,7 +216,9 @@ def tier0_mirror_auth(request):
|
|||||||
token = credentials[1]
|
token = credentials[1]
|
||||||
|
|
||||||
groups = Group.objects.filter(name__in=SELECTED_GROUPS)
|
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:
|
if not user:
|
||||||
return unauthorized
|
return unauthorized
|
||||||
|
|
||||||
|
@@ -1,20 +1,23 @@
|
|||||||
version: '2'
|
version: '2'
|
||||||
|
|
||||||
# Run the following once:
|
# Run the following once:
|
||||||
# docker compose run --rm packages_web python manage.py migrate
|
# docker compose run --rm archweb_web python manage.py migrate
|
||||||
# docker compose run --rm packages_web python manage.py loaddata main/fixtures/arches.json
|
# docker compose run --rm archweb_web python manage.py loaddata mirrors/fixtures/mirrorprotocols.json
|
||||||
# docker compose run --rm packages_web python manage.py loaddata main/fixtures/repos.json
|
# docker compose run --rm archweb_web python manage.py loaddata main/fixtures/arches.json
|
||||||
# docker compose run --rm packages_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local
|
# docker compose run --rm archweb_web python manage.py loaddata main/fixtures/repos.json
|
||||||
|
# docker compose run --rm archweb_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local
|
||||||
## go to /admin and create a user according to overlay/devel/fixtures/user_profiles.json
|
## go to /admin and create a user according to overlay/devel/fixtures/user_profiles.json
|
||||||
## go to /admin/auth/user/2/change/ and add a name
|
## go to /admin/auth/user/2/change/ and add a name
|
||||||
# docker compose run --rm packages_web python manage.py generate_keyring pgp.surfnet.nl ./config/keyring
|
# docker compose run --rm archweb_web python manage.py generate_keyring pgp.surfnet.nl ./config/keyring
|
||||||
# docker compose run --rm packages_web python manage.py pgp_import ./config/keyring
|
# docker compose run --rm archweb_web python manage.py pgp_import ./config/keyring
|
||||||
## go to /admin/devel/developerkey/ and set the owner (and parent) for the ownerless key
|
## go to /admin/devel/developerkey/ and set the owner (and parent) for the ownerless key
|
||||||
## go to /admin/sites/site/1/change/ and set the domain
|
## go to /admin/sites/site/1/change/ and set the domain
|
||||||
|
## clone the mirrors repo
|
||||||
|
# docker compose run --rm archweb_web python manage.py loaddata /mirrors/mirrors.fixture.json
|
||||||
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
packages_web:
|
archweb_web:
|
||||||
container_name: artixweb-packages
|
container_name: artixweb-packages
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
@@ -25,7 +28,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./config:/usr/src/web/config
|
- ./config:/usr/src/web/config
|
||||||
|
|
||||||
packages_sync:
|
archweb_sync:
|
||||||
container_name: artixweb-sync
|
container_name: artixweb-sync
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
@@ -35,11 +38,11 @@ services:
|
|||||||
- ./config:/usr/src/web/config
|
- ./config:/usr/src/web/config
|
||||||
command: ./downloadpackages.sh
|
command: ./downloadpackages.sh
|
||||||
|
|
||||||
packages_nginx:
|
archweb_nginx:
|
||||||
container_name: artixweb-nginx
|
container_name: artixweb-nginx
|
||||||
image: linuxserver/nginx:latest
|
image: linuxserver/nginx:latest
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/config/nginx/site-confs/default.conf:ro
|
- ./nginx.conf:/config/nginx/site-confs/default.conf
|
@@ -8,7 +8,7 @@ fi
|
|||||||
|
|
||||||
printf "downloadpackages.sh\nusing %s/\$repo/os/\$arch for mirror.\n" "$mirror"
|
printf "downloadpackages.sh\nusing %s/\$repo/os/\$arch for mirror.\n" "$mirror"
|
||||||
|
|
||||||
repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins"
|
repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins system-goblins world-goblins galaxy-goblins lib32-goblins"
|
||||||
|
|
||||||
mkdir -p ./archives
|
mkdir -p ./archives
|
||||||
|
|
||||||
|
@@ -11,6 +11,18 @@
|
|||||||
"testing": true
|
"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,
|
"pk": 1,
|
||||||
"model": "main.repo",
|
"model": "main.repo",
|
||||||
@@ -118,5 +130,17 @@
|
|||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 16,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 10,
|
||||||
|
"staging": true,
|
||||||
|
"name": "Core-Staging",
|
||||||
|
"bugs_project": 1,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -280,7 +280,8 @@ class Package(models.Model):
|
|||||||
dep_pkgs = list(dep_pkgs)
|
dep_pkgs = list(dep_pkgs)
|
||||||
dep = dep_pkgs[0]
|
dep = dep_pkgs[0]
|
||||||
if len(dep_pkgs) > 1:
|
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:
|
if len(dep_pkgs) > 0:
|
||||||
dep = dep_pkgs[0]
|
dep = dep_pkgs[0]
|
||||||
trimmed.append(dep)
|
trimmed.append(dep)
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import cssmin
|
|
||||||
import jsmin
|
import jsmin
|
||||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
|
import cssmin
|
||||||
|
|
||||||
|
|
||||||
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
|
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
|
||||||
"""
|
"""
|
||||||
|
@@ -9,24 +9,24 @@ register = template.Library()
|
|||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def jquery():
|
def jquery():
|
||||||
version = '3.6.0'
|
version = '3.6.0'
|
||||||
filename = 'jquery-%s.min.js' % version
|
filename = f'jquery-{version}.min.js'
|
||||||
link = staticfiles_storage.url(filename)
|
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
|
@register.simple_tag
|
||||||
def jquery_tablesorter():
|
def jquery_tablesorter():
|
||||||
version = '2.31.0'
|
version = '2.31.0'
|
||||||
filename = 'jquery.tablesorter-%s.min.js' % version
|
filename = f'jquery.tablesorter-{version}.min.js'
|
||||||
link = staticfiles_storage.url(filename)
|
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
|
@register.simple_tag
|
||||||
def d3js():
|
def d3js():
|
||||||
version = '3.5.0'
|
version = '3.5.0'
|
||||||
filename = 'd3-%s.min.js' % version
|
filename = f'd3-{version}.min.js'
|
||||||
link = staticfiles_storage.url(filename)
|
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:
|
# vim: set ts=4 sw=4 et:
|
||||||
|
@@ -8,8 +8,11 @@ register = template.Library()
|
|||||||
def country_flag(country):
|
def country_flag(country):
|
||||||
if not country:
|
if not country:
|
||||||
return ''
|
return ''
|
||||||
return format_html('<span class="fam-flag fam-flag-%s" title="%s"></span> ' % (
|
return format_html(
|
||||||
str(country.code).lower(), str(country.name)))
|
'<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:
|
# vim: set ts=4 sw=4 et:
|
||||||
|
@@ -10,7 +10,7 @@ def format_key(key_id):
|
|||||||
if len(key_id) in (8, 20):
|
if len(key_id) in (8, 20):
|
||||||
return '0x%s' % key_id
|
return '0x%s' % key_id
|
||||||
elif len(key_id) == 40:
|
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
|
# double space, then 5 more groups of 4 hex chars
|
||||||
split = tuple(key_id[i:i + 4] for i in range(0, 40, 4))
|
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]))
|
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)
|
key_id = pad_key_id(key_id)
|
||||||
if not key_id:
|
if not key_id:
|
||||||
return "Unknown"
|
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>'
|
link_text = f'<div class="pgp-key-ids">{link_text}</div>'
|
||||||
return pgp_key_link(key_id, link_text)
|
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)
|
return format_key(key_id)
|
||||||
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
|
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
|
||||||
scheme = 'https' if pgp_server_secure else 'http'
|
scheme = 'https' if pgp_server_secure else 'http'
|
||||||
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme, pgp_server, key_id)
|
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme,
|
||||||
|
pgp_server,
|
||||||
|
key_id)
|
||||||
if link_text is None:
|
if link_text is None:
|
||||||
link_text = '0x%s' % key_id[-8:]
|
link_text = '0x%s' % key_id[-8:]
|
||||||
values = (url, format_key(key_id), link_text)
|
return format_html('<a href="{url}" title="PGP key search for {key}">{content}</a>',
|
||||||
return format_html('<a href="%s" title="PGP key search for %s">%s</a>' % values)
|
url=url,
|
||||||
|
key=format_key(key_id),
|
||||||
|
content=link_text)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
|
@@ -25,5 +25,14 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"protocol": "https"
|
"protocol": "https"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 9,
|
||||||
|
"model": "mirrors.mirrorprotocol",
|
||||||
|
"fields": {
|
||||||
|
"is_download": false,
|
||||||
|
"default": false,
|
||||||
|
"protocol": "ftp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -184,12 +184,26 @@ def check_rsync_url(mirror_url, location, timeout):
|
|||||||
with open(os.devnull, 'w') as devnull:
|
with open(os.devnull, 'w') as devnull:
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("rsync cmd: %s", ' '.join(rsync_cmd))
|
logger.debug("rsync cmd: %s", ' '.join(rsync_cmd))
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE)
|
timeout_expired = False
|
||||||
_, errdata = proc.communicate()
|
# add an arbitrary 5-second buffer to ensure the process completes and to catch actual rsync timeouts.
|
||||||
end = time.time()
|
rsync_subprocess_timeout = timeout + 5
|
||||||
log.duration = end - start
|
try:
|
||||||
if proc.returncode != 0:
|
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)
|
logger.debug("error: %s, %s", url, errdata)
|
||||||
log.is_success = False
|
log.is_success = False
|
||||||
log.error = errdata.strip().decode('utf-8')
|
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
|
# don't record a duration as it is misleading
|
||||||
if proc.returncode in (1, 30, 35):
|
if proc.returncode in (1, 30, 35):
|
||||||
log.duration = None
|
log.duration = None
|
||||||
else:
|
elif not timeout_expired:
|
||||||
logger.debug("success: %s, %.2f", url, log.duration)
|
logger.debug("success: %s, %.2f", url, log.duration)
|
||||||
if os.path.exists(lastsync_path):
|
if os.path.exists(lastsync_path):
|
||||||
with open(lastsync_path, 'r') as lastsync:
|
with open(lastsync_path, 'r') as lastsync:
|
||||||
|
@@ -27,7 +27,7 @@ def test_mirrorurl_get_full_url(mirrorurl):
|
|||||||
|
|
||||||
def test_mirror_url_clean(mirrorurl):
|
def test_mirror_url_clean(mirrorurl):
|
||||||
mirrorurl.clean()
|
mirrorurl.clean()
|
||||||
# TOOD(jelle): this expects HOSTNAME to resolve, maybe mock
|
# TODO(jelle): this expects HOSTNAME to resolve, maybe mock
|
||||||
assert mirrorurl.has_ipv4
|
assert mirrorurl.has_ipv4
|
||||||
# requires ipv6 on host... mock?
|
# requires ipv6 on host... mock?
|
||||||
# assert mirrorurl.has_ipv6 == True
|
# assert mirrorurl.has_ipv6 == True
|
||||||
|
@@ -54,10 +54,12 @@ class NewsCreateView(CreateView):
|
|||||||
if settings.MAILMAN_PASSWORD:
|
if settings.MAILMAN_PASSWORD:
|
||||||
headers['Approved'] = settings.MAILMAN_PASSWORD
|
headers['Approved'] = settings.MAILMAN_PASSWORD
|
||||||
template = loader.get_template('news/news_email_notification.txt')
|
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(
|
EmailMessage(
|
||||||
subject=f'[arch-announce] {newsitem.title}',
|
subject=f'[arch-announce] {newsitem.title}',
|
||||||
body=template.render(ctx),
|
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],
|
to=[settings.ANNOUNCE_EMAIL],
|
||||||
headers=headers).send()
|
headers=headers).send()
|
||||||
return super(NewsCreateView, self).form_valid(form)
|
return super(NewsCreateView, self).form_valid(form)
|
||||||
|
@@ -33,7 +33,7 @@ server {
|
|||||||
try_files "" @proxy;
|
try_files "" @proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/(packages|groups|opensearch|feeds) {
|
location ~ ^/(packages|groups|opensearch|feeds|mirrors|mirrorlist) {
|
||||||
try_files "" @proxy;
|
try_files "" @proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,4 +65,4 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 501 502 503 504 /500.html;
|
error_page 500 501 502 503 504 /500.html;
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "World-Gremlins",
|
"name": "world-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Galaxy-Gremlins",
|
"name": "galaxy-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "System",
|
"name": "system",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "World",
|
"name": "world",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Galaxy",
|
"name": "galaxy",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Lib32",
|
"name": "lib32",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Lib32-Gremlins",
|
"name": "lib32-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -89,10 +89,58 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "System-Gremlins",
|
"name": "system-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 9,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "system-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 10,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "world-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 11,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "galaxy-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 12,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "lib32-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -52,8 +52,9 @@ def create_specification(package, log, finder):
|
|||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
def get_last_log(repo, pkgbase):
|
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
|
# 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)
|
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'
|
url = f'https://{settings.GITLAB_INSTANCE}/api/v4/projects/{pkgrepo}/repository/tags'
|
||||||
|
|
||||||
@@ -65,8 +66,12 @@ def get_last_log(repo, pkgbase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
tags = r.json()
|
tags = r.json()
|
||||||
|
|
||||||
|
# filter out unrelated tags
|
||||||
|
tags = [tag for tag in tags if tag["name"] == version]
|
||||||
|
|
||||||
if len(tags) == 0:
|
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
|
return None
|
||||||
|
|
||||||
tag = tags[0]
|
tag = tags[0]
|
||||||
@@ -89,7 +94,7 @@ def add_signoff_comments():
|
|||||||
if not group.default_spec:
|
if not group.default_spec:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log = get_last_log(group.repo, group.pkgbase)
|
log = get_tag_info(group.repo, group.pkgbase, group.version)
|
||||||
if log is None:
|
if log is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -108,7 +113,8 @@ def cleanup_signoff_comments():
|
|||||||
id_signoffs = [signoff.id for g in groups for signoff in g.signoffs]
|
id_signoffs = [signoff.id for g in groups for signoff in g.signoffs]
|
||||||
logger.info("Keeping %s signoffs", len(id_signoffs))
|
logger.info("Keeping %s signoffs", len(id_signoffs))
|
||||||
# FakeSignoffSpecification's have no id
|
# 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))
|
logger.info("Keeping %s signoffspecifications", len(id_signoffspecs))
|
||||||
|
|
||||||
Signoff.objects.exclude(id__in=id_signoffs).delete()
|
Signoff.objects.exclude(id__in=id_signoffs).delete()
|
||||||
|
@@ -253,7 +253,8 @@ class UpdateManager(models.Manager):
|
|||||||
if new_pkg:
|
if new_pkg:
|
||||||
update.action_flag = CHANGE
|
update.action_flag = CHANGE
|
||||||
# ensure we should even be logging this
|
# 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
|
# all relevant fields were the same; e.g. a force update
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -395,7 +396,8 @@ class RelatedToBase(models.Model):
|
|||||||
# actually satisfy the requirements
|
# actually satisfy the requirements
|
||||||
if self.comparison and self.version:
|
if self.comparison and self.version:
|
||||||
alpm = AlpmAPI()
|
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:
|
if len(pkgs) == 0:
|
||||||
# couldn't find a package in the DB
|
# couldn't find a package in the DB
|
||||||
# it should be a virtual depend (or a removed package)
|
# it should be a virtual depend (or a removed package)
|
||||||
|
@@ -43,8 +43,8 @@ def pkg_details_link(pkg, link_title=None, honor_flagged=False):
|
|||||||
link_content = link_title
|
link_content = link_title
|
||||||
if honor_flagged and pkg.flag_date:
|
if honor_flagged and pkg.flag_date:
|
||||||
link_content = '<span class="flagged">%s</span>' % link_title
|
link_content = '<span class="flagged">%s</span>' % link_title
|
||||||
link = '<a href="%s" title="View package details for %s">%s</a>'
|
link = '<a href="{link}" title="View package details for {pkgname}">{content}</a>'
|
||||||
return format_html(link % (pkg.get_absolute_url(), pkg.pkgname, link_content))
|
return format_html(link, link=pkg.get_absolute_url(), pkgname=pkg.pkgname, content=link_content)
|
||||||
|
|
||||||
|
|
||||||
# vim: set ts=4 sw=4 et:
|
# vim: set ts=4 sw=4 et:
|
||||||
|
@@ -26,9 +26,9 @@ class RematchDeveloperTest(TransactionTestCase):
|
|||||||
self.package.delete()
|
self.package.delete()
|
||||||
|
|
||||||
def test_basic(self):
|
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'
|
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')
|
call_command('populate_signoffs')
|
||||||
|
|
||||||
signoff_spec = SignoffSpecification.objects.first()
|
signoff_spec = SignoffSpecification.objects.first()
|
||||||
@@ -36,8 +36,8 @@ class RematchDeveloperTest(TransactionTestCase):
|
|||||||
assert signoff_spec.pkgbase == self.package.pkgbase
|
assert signoff_spec.pkgbase == self.package.pkgbase
|
||||||
|
|
||||||
def test_invalid(self):
|
def test_invalid(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:
|
||||||
get_last_log.return_value = None
|
get_tag_info.return_value = None
|
||||||
call_command('populate_signoffs')
|
call_command('populate_signoffs')
|
||||||
|
|
||||||
assert SignoffSpecification.objects.count() == 0
|
assert SignoffSpecification.objects.count() == 0
|
||||||
|
@@ -67,6 +67,7 @@ def test_sort(client, package):
|
|||||||
def test_packages(client, package):
|
def test_packages(client, package):
|
||||||
response = client.get('/opensearch/packages/')
|
response = client.get('/opensearch/packages/')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert 'template="http://example.com/opensearch/packages/"' in response.content.decode()
|
||||||
|
|
||||||
|
|
||||||
def test_packages_suggest(client, package):
|
def test_packages_suggest(client, package):
|
||||||
|
@@ -5,6 +5,7 @@ from collections import defaultdict
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
@@ -21,10 +22,10 @@ from ..utils import get_wrong_permissions, multilib_differences
|
|||||||
@require_safe
|
@require_safe
|
||||||
@cache_control(public=True, max_age=86400)
|
@cache_control(public=True, max_age=86400)
|
||||||
def opensearch(request):
|
def opensearch(request):
|
||||||
domain = "%s://%s" % ('https', request.META.get('HTTP_HOST'))
|
current_site = Site.objects.get_current()
|
||||||
|
|
||||||
return render(request, 'packages/opensearch.xml',
|
return render(request, 'packages/opensearch.xml',
|
||||||
{'domain': domain},
|
{'domain': f'{request.scheme}://{current_site.domain}'},
|
||||||
content_type='application/opensearchdescription+xml')
|
content_type='application/opensearchdescription+xml')
|
||||||
|
|
||||||
|
|
||||||
@@ -140,8 +141,14 @@ def sonames(request):
|
|||||||
name = request.GET.get('name')
|
name = request.GET.get('name')
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname', 'pkg__pkgver', 'pkg__pkgrel', 'pkg__epoch', 'pkg__repo__name')
|
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname',
|
||||||
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]
|
'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:
|
else:
|
||||||
return HttpResponseBadRequest('name parameter is required')
|
return HttpResponseBadRequest('name parameter is required')
|
||||||
|
|
||||||
|
@@ -38,7 +38,8 @@ class FlagForm(forms.Form):
|
|||||||
# make sure the message isn't garbage (only punctuation or whitespace)
|
# make sure the message isn't garbage (only punctuation or whitespace)
|
||||||
# or spam (using a simple denylist)
|
# or spam (using a simple denylist)
|
||||||
# and ensure a certain minimum length
|
# 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.")
|
raise forms.ValidationError("Enter a valid and useful out-of-date message.")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@@ -50,7 +50,8 @@ class Migration(migrations.Migration):
|
|||||||
('author', models.CharField(max_length=255)),
|
('author', models.CharField(max_length=255)),
|
||||||
('publishdate', models.DateTimeField(db_index=True, verbose_name='publish date')),
|
('publishdate', models.DateTimeField(db_index=True, verbose_name='publish date')),
|
||||||
('url', models.CharField(max_length=255, verbose_name='URL')),
|
('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={
|
options={
|
||||||
'verbose_name_plural': 'Feed Items',
|
'verbose_name_plural': 'Feed Items',
|
||||||
|
@@ -14,6 +14,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='feeditem',
|
model_name='feeditem',
|
||||||
name='feed',
|
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'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -8,7 +8,7 @@ fi
|
|||||||
|
|
||||||
printf "populatepackages.sh\nretrieving package files from %s\n" "$path"
|
printf "populatepackages.sh\nretrieving package files from %s\n" "$path"
|
||||||
|
|
||||||
repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins"
|
repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins system-goblins world-goblins galaxy-goblins lib32-goblins"
|
||||||
|
|
||||||
for repo in $repos
|
for repo in $repos
|
||||||
do
|
do
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
@@ -25,12 +26,12 @@ def index(request):
|
|||||||
else:
|
else:
|
||||||
def updates():
|
def updates():
|
||||||
return get_recent_updates()
|
return get_recent_updates()
|
||||||
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST'))
|
current_site = Site.objects.get_current()
|
||||||
context = {
|
context = {
|
||||||
'news_updates': News.objects.order_by('-postdate', '-id')[:15],
|
'news_updates': News.objects.order_by('-postdate', '-id')[:15],
|
||||||
'pkg_updates': updates,
|
'pkg_updates': updates,
|
||||||
'staff_groups': StaffGroup.objects.all(),
|
'staff_groups': StaffGroup.objects.all(),
|
||||||
'domain': domain,
|
'domain': f'{request.scheme}://{current_site.domain}',
|
||||||
}
|
}
|
||||||
return render(request, 'public/index.html', context)
|
return render(request, 'public/index.html', context)
|
||||||
|
|
||||||
@@ -92,12 +93,13 @@ def feeds(request):
|
|||||||
@cache_control(max_age=307)
|
@cache_control(max_age=307)
|
||||||
def keys(request):
|
def keys(request):
|
||||||
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
|
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
|
||||||
users = User.objects.filter(
|
users = User.objects.filter(is_active=True,
|
||||||
is_active=True, userprofile__id__in=profile_ids).order_by('first_name', 'last_name').select_related('userprofile')
|
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
|
user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users
|
||||||
if user.userprofile.pgp_key)
|
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',
|
master_keys = MasterKey.objects.select_related('owner', 'revoker',
|
||||||
'owner__userprofile', 'revoker__userprofile').filter(
|
'owner__userprofile', 'revoker__userprofile').filter(
|
||||||
revoked__isnull=True)
|
revoked__isnull=True)
|
||||||
@@ -153,7 +155,7 @@ def keys_json(request):
|
|||||||
'group': 'master'
|
'group': 'master'
|
||||||
} for key in master_keys)
|
} 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)
|
signatures = PGPSignature.objects.filter(not_expired, revoked__isnull=True)
|
||||||
edge_list = [{ 'signee': sig.signee, 'signer': sig.signer }
|
edge_list = [{ 'signee': sig.signee, 'signer': sig.signer }
|
||||||
for sig in signatures]
|
for sig in signatures]
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from bencode import bencode
|
from bencode import bencode
|
||||||
@@ -24,7 +24,7 @@ def torrent_data():
|
|||||||
data = {
|
data = {
|
||||||
'comment': 'comment',
|
'comment': 'comment',
|
||||||
'created_by': 'Arch Linux',
|
'created_by': 'Arch Linux',
|
||||||
'creation date': int(datetime.utcnow().timestamp()),
|
'creation date': int(datetime.now(timezone.utc).timestamp()),
|
||||||
'info': {
|
'info': {
|
||||||
'name': 'arch.iso',
|
'name': 'arch.iso',
|
||||||
'length': 1,
|
'length': 1,
|
||||||
|
@@ -9,8 +9,10 @@ from .views import ReleaseDetailView, ReleaseListView
|
|||||||
releases_patterns = [
|
releases_patterns = [
|
||||||
path('', ReleaseListView.as_view(), name='releng-release-list'),
|
path('', ReleaseListView.as_view(), name='releng-release-list'),
|
||||||
path('json/', views.releases_json, name='releng-release-list-json'),
|
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]+)/$', cache_page(311)(ReleaseDetailView.as_view()),
|
||||||
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), name='releng-release-torrent'),
|
name='releng-release-detail'),
|
||||||
|
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent),
|
||||||
|
name='releng-release-torrent'),
|
||||||
]
|
]
|
||||||
|
|
||||||
netboot_patterns = [
|
netboot_patterns = [
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
|
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
|
||||||
Django==5.0.8
|
Django==5.1.10
|
||||||
IPy==1.1
|
IPy==1.1
|
||||||
Markdown==3.3.7
|
Markdown==3.3.7
|
||||||
bencode.py==4.0.0
|
bencode.py==4.0.0
|
||||||
django-countries==7.6.1
|
django-countries==7.6.1
|
||||||
django-extensions==3.2.3
|
django-extensions==4.1
|
||||||
jsmin==3.0.1
|
jsmin==3.0.1
|
||||||
pgpdump==1.5
|
pgpdump==1.5
|
||||||
parse==1.19.0
|
parse==1.20.2
|
||||||
sqlparse==0.5.0
|
sqlparse==0.5.0
|
||||||
django-csp==3.7
|
django-csp==4.0
|
||||||
ptpython==2.0.4
|
ptpython==2.0.4
|
||||||
feedparser==6.0.10
|
feedparser==6.0.11
|
||||||
bleach==6.0.0
|
bleach==6.0.0
|
||||||
requests==2.32.0
|
requests==2.32.4
|
||||||
xtarfile==0.1.0
|
xtarfile==0.2.1
|
||||||
zstandard==0.17.0
|
zstandard==0.23.0
|
||||||
whitenoise==6.7.0
|
whitenoise==6.8.2
|
||||||
django-prometheus==2.3.1
|
django-prometheus==2.3.1
|
||||||
|
@@ -24,7 +24,6 @@ select = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # line lengt violation
|
|
||||||
"E731", # Do not assign a `lambda` expression, use a `def`
|
"E731", # Do not assign a `lambda` expression, use a `def`
|
||||||
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
|
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
|
||||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||||
@@ -34,3 +33,8 @@ ignore = [
|
|||||||
"DJ008", # Model does not define `__str__` method
|
"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`
|
"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
|
||||||
|
]
|
||||||
|
13
settings.py
13
settings.py
@@ -41,9 +41,6 @@ SITE_ID = 1
|
|||||||
DATE_FORMAT = 'Y-m-d'
|
DATE_FORMAT = 'Y-m-d'
|
||||||
DATETIME_FORMAT = 'Y-m-d H:i'
|
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 configuration
|
||||||
LOGIN_URL = '/login/'
|
LOGIN_URL = '/login/'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
@@ -270,8 +267,16 @@ if DEBUG_TOOLBAR:
|
|||||||
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
|
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
|
||||||
|
|
||||||
if PROMETHEUS_METRICS:
|
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']
|
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:
|
# vim: set ts=4 sw=4 et:
|
||||||
|
@@ -754,6 +754,10 @@ table.results {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.results [hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* pkglist: layout */
|
/* pkglist: layout */
|
||||||
#pkglist-about {
|
#pkglist-about {
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
@@ -1056,12 +1060,12 @@ table td.country {
|
|||||||
background: #ffd;
|
background: #ffd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results tr:nth-child(even),
|
.results tr:nth-child(even of :not([hidden])),
|
||||||
#article-list tr:nth-child(even) {
|
#article-list tr:nth-child(even) {
|
||||||
background: #e4eeff;
|
background: #e4eeff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results tr:nth-child(odd),
|
.results tr:nth-child(odd of :not([hidden])),
|
||||||
#article-list tr:nth-child(odd) {
|
#article-list tr:nth-child(odd) {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
@@ -212,8 +212,14 @@ function filter_pkgs_list(filter_ele, tbody_ele) {
|
|||||||
rows = rows.has('.incomplete');
|
rows = rows.has('.incomplete');
|
||||||
}
|
}
|
||||||
/* hide all rows, then show the set we care about */
|
/* hide all rows, then show the set we care about */
|
||||||
all_rows.hide();
|
// note that we don't use .hide() from jQuery because it adds display:none
|
||||||
rows.show();
|
// 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);
|
$('#filter-count').text(rows.length);
|
||||||
/* make sure we update the odd/even styling from sorting */
|
/* make sure we update the odd/even styling from sorting */
|
||||||
$('.results').trigger('applyWidgets', [false]);
|
$('.results').trigger('applyWidgets', [false]);
|
||||||
@@ -318,6 +324,10 @@ function filter_signoffs() {
|
|||||||
/* start with all rows, and then remove ones we shouldn't show */
|
/* start with all rows, and then remove ones we shouldn't show */
|
||||||
var rows = $('#tbody_signoffs').children(),
|
var rows = $('#tbody_signoffs').children(),
|
||||||
all_rows = rows;
|
all_rows = rows;
|
||||||
|
/* apply the filters, cheaper ones first */
|
||||||
|
if ($('#id_mine_only').is(':checked')) {
|
||||||
|
rows = rows.filter('.mine');
|
||||||
|
}
|
||||||
/* apply arch and repo filters */
|
/* apply arch and repo filters */
|
||||||
$('#signoffs_filter .arch_filter').add(
|
$('#signoffs_filter .arch_filter').add(
|
||||||
'#signoffs_filter .repo_filter').each(function() {
|
'#signoffs_filter .repo_filter').each(function() {
|
||||||
@@ -330,8 +340,14 @@ function filter_signoffs() {
|
|||||||
rows = rows.has('td.signoff-no');
|
rows = rows.has('td.signoff-no');
|
||||||
}
|
}
|
||||||
/* hide all rows, then show the set we care about */
|
/* hide all rows, then show the set we care about */
|
||||||
all_rows.hide();
|
// note that we don't use .hide() from jQuery because it adds display:none
|
||||||
rows.show();
|
// 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);
|
$('#filter-count').text(rows.length);
|
||||||
/* make sure we update the odd/even styling from sorting */
|
/* make sure we update the odd/even styling from sorting */
|
||||||
$('.results').trigger('applyWidgets', [false]);
|
$('.results').trigger('applyWidgets', [false]);
|
||||||
@@ -340,6 +356,7 @@ function filter_signoffs() {
|
|||||||
function filter_signoffs_reset() {
|
function filter_signoffs_reset() {
|
||||||
$('#signoffs_filter .arch_filter').prop('checked', true);
|
$('#signoffs_filter .arch_filter').prop('checked', true);
|
||||||
$('#signoffs_filter .repo_filter').prop('checked', true);
|
$('#signoffs_filter .repo_filter').prop('checked', true);
|
||||||
|
$('#id_mine_only').prop('checked', false);
|
||||||
$('#id_pending').prop('checked', false);
|
$('#id_pending').prop('checked', false);
|
||||||
filter_signoffs();
|
filter_signoffs();
|
||||||
}
|
}
|
||||||
|
@@ -145,6 +145,10 @@ tr :nth-child(7) {
|
|||||||
background-image: url(data:image/gif;base64,R0lGODlhFQAEAPABAOTu/wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAgABACwAAAAAFQAEAAACDYwfoAvoz9qbZ9FrJC0AOw==);
|
background-image: url(data:image/gif;base64,R0lGODlhFQAEAPABAOTu/wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAgABACwAAAAAFQAEAAACDYwfoAvoz9qbZ9FrJC0AOw==);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #334450;
|
||||||
|
}
|
||||||
|
|
||||||
.results.results td,
|
.results.results td,
|
||||||
.results.results th {
|
.results.results th {
|
||||||
border: 1px solid #858585;
|
border: 1px solid #858585;
|
||||||
@@ -158,7 +162,7 @@ tr :nth-child(7) {
|
|||||||
background-color: #111;
|
background-color: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results th {
|
.results th, #pkgsearch {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #0f3147;
|
background-color: #0f3147;
|
||||||
border: 1px solid #0A6682;
|
border: 1px solid #0A6682;
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
@@ -30,7 +30,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in admin_log %}
|
{% for entry in admin_log %}
|
||||||
<tr>
|
<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 %}
|
{% if log_user %}
|
||||||
<td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td>
|
<td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@@ -36,8 +36,8 @@
|
|||||||
{% endif %}{% endwith %}</td>
|
{% endif %}{% endwith %}</td>
|
||||||
<td>{{ pkg.repo.name }}</td>
|
<td>{{ pkg.repo.name }}</td>
|
||||||
<td>{{ pkg.arch.name }}</td>
|
<td>{{ pkg.arch.name }}</td>
|
||||||
<td>{{ pkg.flag_date|date }}</td>
|
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.last_update|date }}</td>
|
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr class="empty"><td colspan="7"><em>No flagged packages to display</em></td></tr>
|
<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.version }}</td>
|
||||||
<td>{{ group.arch.name }}</td>
|
<td>{{ group.arch.name }}</td>
|
||||||
<td>{{ group.target_repo }}</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 %}
|
{% if group.specification.known_bad %}
|
||||||
<td class="approval signoff-bad">Bad</td>
|
<td class="approval signoff-bad">Bad</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="wrap"><a href="{{ todo.get_absolute_url }}"
|
<td class="wrap"><a href="{{ todo.get_absolute_url }}"
|
||||||
title="View todo list: {{ todo.name }}">{{ todo.name }}</a></td>
|
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.creator.get_full_name }}</td>
|
||||||
<td>{{ todo.pkg_count }}</td>
|
<td>{{ todo.pkg_count }}</td>
|
||||||
<td>{{ todo.incomplete_count }}</td>
|
<td>{{ todo.incomplete_count }}</td>
|
||||||
|
@@ -60,9 +60,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ pkg.full_version }}</td>
|
<td>{{ pkg.full_version }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ pkg.last_update|date }}</td>
|
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.build_date|date }}</td>
|
<td>{{ pkg.build_date|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.flag_date|date }}</td>
|
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
|
||||||
{% for attr in column_attrs %}
|
{% for attr in column_attrs %}
|
||||||
<td>{{ pkg|attribute:attr }}</td>
|
<td>{{ pkg|attribute:attr }}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
|
|
||||||
<h2>Tier 0 Mirror usage information</h2>
|
<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 %}
|
{% if mirror_url %}
|
||||||
<code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button>
|
<code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button>
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for item in news_list %}
|
{% for item in news_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ item.postdate|date }}</td>
|
<td>{{ item.postdate|date:"Y-m-d" }}</td>
|
||||||
<td class="wrap"><a href="{{ item.get_absolute_url }}"
|
<td class="wrap"><a href="{{ item.get_absolute_url }}"
|
||||||
title="View: {{ item.title }}">{{ item.title }}</a></td>
|
title="View: {{ item.title }}">{{ item.title }}</a></td>
|
||||||
<td>{{ item.author.get_full_name }}</td>
|
<td>{{ item.author.get_full_name }}</td>
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% 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 class="article-content" itemprop="articleBody">{{ news.html }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<th>Multilib Version</th>
|
<th>Multilib Version</th>
|
||||||
<th>x86_64 Version</th>
|
<th>x86_64 Version</th>
|
||||||
<th>x86_64 Name</th>
|
<th>x86_64 Name</th>
|
||||||
<th>x864_ Repo</th>
|
<th>x86_64 Repo</th>
|
||||||
<th>Multilib Last Updated</th>
|
<th>Multilib Last Updated</th>
|
||||||
<th>x86_64 Last Updated</th>
|
<th>x86_64 Last Updated</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
<td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td>
|
<td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td>
|
||||||
<td>{% pkg_details_link pkg2 %}</td>
|
<td>{% pkg_details_link pkg2 %}</td>
|
||||||
<td>{{ pkg2.repo }}</td>
|
<td>{{ pkg2.repo }}</td>
|
||||||
<td>{{ pkg1.last_update|date }}</td>
|
<td>{{ pkg1.last_update|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg2.last_update|date }}</td>
|
<td>{{ pkg2.last_update|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
<td><a href="/groups/{{ grp.arch }}/{{ grp.name }}/"
|
<td><a href="/groups/{{ grp.arch }}/{{ grp.name }}/"
|
||||||
title="Group details for {{ grp.name }}">{{ grp.name }}</a></td>
|
title="Group details for {{ grp.name }}">{{ grp.name }}</a></td>
|
||||||
<td>{{ grp.count }}</td>
|
<td>{{ grp.count }}</td>
|
||||||
<td>{{ grp.last_update|date }}</td>
|
<td>{{ grp.last_update|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if pkg.flag_date %}
|
{% 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 %}
|
{% with tp=pkg.in_testing %}{% if tp %}
|
||||||
<li><span class="flagged">Version
|
<li><span class="flagged">Version
|
||||||
<a href="{{ tp.get_absolute_url }}"
|
<a href="{{ tp.get_absolute_url }}"
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
title="Browse packages for {{ pkg.arch.name }} architecture">{{ pkg.arch.name }}</a></td>
|
title="Browse packages for {{ pkg.arch.name }} architecture">{{ pkg.arch.name }}</a></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Repository:</th>
|
<th>Repository:</th>
|
||||||
<td><a href="/packages/?repo={{ pkg.repo.name|capfirst }}"
|
<td><a href="/packages/?repo={{ pkg.repo.name }}"
|
||||||
title="Browse the {{ pkg.repo.name|capfirst }} repository">{{ pkg.repo.name|capfirst }}</a></td>
|
title="Browse the {{ pkg.repo.name }} repository">{{ pkg.repo.name }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if pkg.pkgname == pkg.pkgbase %}
|
{% if pkg.pkgname == pkg.pkgbase %}
|
||||||
{% with splits=pkg.split_packages %}{% if splits %}
|
{% with splits=pkg.split_packages %}{% if splits %}
|
||||||
@@ -188,19 +188,19 @@
|
|||||||
{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
|
{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Build Date:</th>
|
<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>
|
</tr>{% if pkg.signature %}<tr>
|
||||||
<th>Signed By:</th>
|
<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>
|
<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>
|
</tr><tr>
|
||||||
<th>Signature Date:</th>
|
<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>
|
</tr>{% else %}<tr>
|
||||||
<th>Signed By:</th>
|
<th>Signed By:</th>
|
||||||
<td>Unsigned</td>
|
<td>Unsigned</td>
|
||||||
</tr>{% endif %}<tr>
|
</tr>{% endif %}<tr>
|
||||||
<th>Last Updated:</th>
|
<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>
|
</tr>
|
||||||
{% if user.is_authenticated %}<tr>
|
{% if user.is_authenticated %}<tr>
|
||||||
<th>Reproducible Status:</th>
|
<th>Reproducible Status:</th>
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr>
|
{% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr>
|
||||||
<th>Last Flag Request:</th>
|
<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>
|
<div class="userdata">{{ flag_request.message|linebreaksbr|default:"{no message}" }}</div></td>
|
||||||
</tr>{% endif %}{% endwith %}{% endif %}
|
</tr>{% endif %}{% endwith %}{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
@@ -33,8 +33,8 @@
|
|||||||
<td>{{ pkg.full_version }}</td>
|
<td>{{ pkg.full_version }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
||||||
<td>{{ pkg.last_update|date }}</td>
|
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.flag_date|date }}</td>
|
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -62,7 +62,7 @@
|
|||||||
{% for pkg in exact_matches %}
|
{% for pkg in exact_matches %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ pkg.arch.name }}</td>
|
<td>{{ pkg.arch.name }}</td>
|
||||||
<td>{{ pkg.repo.name|capfirst }}</td>
|
<td>{{ pkg.repo.name }}</td>
|
||||||
<td>{% pkg_details_link pkg %}</td>
|
<td>{% pkg_details_link pkg %}</td>
|
||||||
{% if pkg.flag_date %}
|
{% if pkg.flag_date %}
|
||||||
<td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
|
<td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
<td>{{ pkg.full_version }}</td>
|
<td>{{ pkg.full_version }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
||||||
<td>{{ pkg.last_update|date }}</td>
|
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.flag_date|date }}</td>
|
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
<td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td>
|
<td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ pkg.arch.name }}</td>
|
<td>{{ pkg.arch.name }}</td>
|
||||||
<td>{{ pkg.repo.name|capfirst }}</td>
|
<td>{{ pkg.repo.name }}</td>
|
||||||
<td>{% pkg_details_link pkg %}</td>
|
<td>{% pkg_details_link pkg %}</td>
|
||||||
{% if pkg.flag_date %}
|
{% if pkg.flag_date %}
|
||||||
<td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
|
<td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
|
||||||
@@ -117,8 +117,8 @@
|
|||||||
<td>{{ pkg.full_version }}</td>
|
<td>{{ pkg.full_version }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
<td class="wrap">{{ pkg.pkgdesc }}</td>
|
||||||
<td>{{ pkg.last_update|date }}</td>
|
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
|
||||||
<td>{{ pkg.flag_date|date }}</td>
|
<td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr>
|
<tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr>
|
||||||
|
@@ -26,6 +26,8 @@
|
|||||||
<div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label>
|
<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>
|
<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 %}
|
{% 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>
|
<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>
|
<input type="checkbox" name="pending" id="id_pending" value="pending"/></div>
|
||||||
<div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div>
|
<div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div>
|
||||||
@@ -50,13 +52,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tbody_signoffs">
|
<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>{% pkg_details_link group.package %} {{ group.version }}</td>
|
||||||
<td>{{ group.arch.name }}</td>
|
<td>{{ group.arch.name }}</td>
|
||||||
<td>{{ group.target_repo }}</td>
|
<td>{{ group.target_repo }}</td>
|
||||||
<td>{{ group.packager|default:"Unknown" }}</td>
|
<td>{{ group.packager|default:"Unknown" }}</td>
|
||||||
<td>{{ group.packages|length }}</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 %}
|
{% if group.specification.known_bad %}
|
||||||
<td class="approval signoff-bad">Bad</td>
|
<td class="approval signoff-bad">Bad</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
<a href="{{ entry.url }}"
|
<a href="{{ entry.url }}"
|
||||||
title="View full article: {{ entry.title }}">{{ entry.title }}</a>
|
title="View full article: {{ entry.title }}">{{ entry.title }}</a>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="timestamp">{{ entry.publishdate|date }}</p>
|
<p class="timestamp">{{ entry.publishdate|date:"Y-m-d" }}</p>
|
||||||
<div class="article-content">
|
<div class="article-content">
|
||||||
{{ entry.summary |safe }}
|
{{ entry.summary |safe }}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -49,6 +49,11 @@
|
|||||||
<td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}"
|
<td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}"
|
||||||
title="Visit the website for {{ dev.get_full_name }}">
|
title="Visit the website for {{ dev.get_full_name }}">
|
||||||
{{ prof.website }}</a>{% endif %}</td>
|
{{ 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>
|
</tr><tr>
|
||||||
<th>Occupation:</th>
|
<th>Occupation:</th>
|
||||||
<td>{{ prof.occupation }}</td>
|
<td>{{ prof.occupation }}</td>
|
||||||
|
@@ -53,37 +53,10 @@
|
|||||||
<img src="{% static "nitrokey_logo.png" %}"
|
<img src="{% static "nitrokey_logo.png" %}"
|
||||||
class="sponsor-btn-nitrokey" title="" alt="Nitrokey logo"/></a>
|
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>
|
<h3>Past donors</h3>
|
||||||
|
|
||||||
|
<p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> & <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p>
|
||||||
|
|
||||||
<div id="donor-list">
|
<div id="donor-list">
|
||||||
<ul>
|
<ul>
|
||||||
{% for donor in donors %}
|
{% for donor in donors %}
|
||||||
|
@@ -77,13 +77,6 @@
|
|||||||
title="Arch Linux Netboot">Arch Linux Netboot</a></li>
|
title="Arch Linux Netboot">Arch Linux Netboot</a></li>
|
||||||
</ul>
|
</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>
|
<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>
|
<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>
|
||||||
@@ -93,6 +86,14 @@
|
|||||||
|
|
||||||
<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>
|
<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>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>
|
<h3 id="http-downloads">HTTP Direct Downloads</h3>
|
||||||
|
|
||||||
<p>In addition to the BitTorrent links above, install images can also be
|
<p>In addition to the BitTorrent links above, install images can also be
|
||||||
@@ -132,15 +133,15 @@
|
|||||||
<pre><code>$ b2sum -c b2sums.txt</code></pre>
|
<pre><code>$ b2sum -c b2sums.txt</code></pre>
|
||||||
|
|
||||||
To verify the PGP signature using Sequoia, first download the release signing key from WKD:
|
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:
|
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:
|
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>
|
<pre><code>$ gpg --auto-key-locate clear,wkd -v --locate-external-key {{ release.wkd_email }}</code></pre>
|
||||||
Verify the signature:
|
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 %}
|
{% cache 600 download-mirrors %}
|
||||||
<div id="download-mirrors">
|
<div id="download-mirrors">
|
||||||
|
@@ -50,7 +50,7 @@
|
|||||||
<a href="{{ news.get_absolute_url }}"
|
<a href="{{ news.get_absolute_url }}"
|
||||||
title="View full article: {{ news.title }}">{{ news.title }}</a>
|
title="View full article: {{ news.title }}">{{ news.title }}</a>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="timestamp">{{ news.postdate|date }}</p>
|
<p class="timestamp">{{ news.postdate|date:"Y-m-d" }}</p>
|
||||||
<div class="article-content">
|
<div class="article-content">
|
||||||
{% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }}
|
{% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }}
|
||||||
{% else %}{{ news.html|truncatewords_html:100 }}{% endif %}
|
{% else %}{{ news.html|truncatewords_html:100 }}{% endif %}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<dl class="newslist">
|
<dl class="newslist">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<dt>{{ news.postdate|date }}</dt>
|
<dt>{{ news.postdate|date:"Y-m-d" }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href="{{ news.get_absolute_url }}"
|
<a href="{{ news.get_absolute_url }}"
|
||||||
title="View full article: {{ news.title }}">{{ news.title }}</a>
|
title="View full article: {{ news.title }}">{{ news.title }}</a>
|
||||||
@@ -133,8 +133,6 @@
|
|||||||
<h4>Support</h4>
|
<h4>Support</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url 'page-donate' %}" title="Help support Arch Linux">Donate</a></li>
|
<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&org=Archlinux"
|
<li><a href="https://www.freewear.org/?page=list_items&org=Archlinux"
|
||||||
title="T-shirts">T-shirts via Freewear</a></li>
|
title="T-shirts">T-shirts via Freewear</a></li>
|
||||||
<li><a href="https://www.hellotux.com/arch"
|
<li><a href="https://www.hellotux.com/arch"
|
||||||
@@ -155,6 +153,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://wiki.archlinux.org/title/Getting_involved"
|
<li><a href="https://wiki.archlinux.org/title/Getting_involved"
|
||||||
title="Getting involved">Getting involved</a></li>
|
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/"
|
<li><a href="https://gitlab.archlinux.org/archlinux/"
|
||||||
title="Official Arch projects (git)">Projects in Git</a></li>
|
title="Official Arch projects (git)">Projects in Git</a></li>
|
||||||
<li><a href="https://wiki.archlinux.org/title/DeveloperWiki"
|
<li><a href="https://wiki.archlinux.org/title/DeveloperWiki"
|
||||||
@@ -202,20 +201,10 @@
|
|||||||
title="" alt="Hetzner logo"/>
|
title="" alt="Hetzner logo"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="https://www.privateinternetaccess.com/" title="Private Internet Access">
|
|
||||||
<img src="{% static "pia_logo.png" %}"
|
|
||||||
 title="" alt="Private Internet Access logo"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://icons8.com/" title="Icons8">
|
<a href="https://icons8.com/" title="Icons8">
|
||||||
<img src="{% static "icons8_logo.png" %}"
|
<img src="{% static "icons8_logo.png" %}"
|
||||||
title="" alt="Icons8 logo"/>
|
title="" alt="Icons8 logo"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="https://www.shells.com" title="Shells.com">
|
|
||||||
<img src="{% static "shells_logo.png" %}"
|
|
||||||
title="" alt="Shells logo"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<h2>{{ release.version }}</h2>
|
<h2>{{ release.version }}</h2>
|
||||||
|
|
||||||
<ul>
|
<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 %}
|
{% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %}
|
||||||
<li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li>
|
<li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li>
|
||||||
{% if release.torrent_data %}
|
{% if release.torrent_data %}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Comment:</strong> {{ torrent.comment }}</li>
|
<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>Created By:</strong> {{ torrent.created_by }}</li>
|
||||||
<li><strong>Announce URL:</strong> {{ torrent.announce }}</li>
|
<li><strong>Announce URL:</strong> {{ torrent.announce }}</li>
|
||||||
<li><strong>File Name:</strong> {{ torrent.file_name }}</li>
|
<li><strong>File Name:</strong> {{ torrent.file_name }}</li>
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
<a href="{{ item.magnet_uri }}"
|
<a href="{{ item.magnet_uri }}"
|
||||||
title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a>
|
title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a>
|
||||||
{% endif %}</td>
|
{% 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><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td>
|
||||||
<td>{{ item.kernel_version|default:"" }}</td>
|
<td>{{ item.kernel_version|default:"" }}</td>
|
||||||
<td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td>
|
<td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td>
|
||||||
|
@@ -37,7 +37,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="wrap"><a href="{{ list.get_absolute_url }}"
|
<td class="wrap"><a href="{{ list.get_absolute_url }}"
|
||||||
title="View todo list: {{ list.name }}">{{ list.name }}</a></td>
|
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.creator.get_full_name }}</td>
|
||||||
<td>{{ list.pkg_count }}</td>
|
<td>{{ list.pkg_count }}</td>
|
||||||
<td>{{ list.incomplete_count }}</td>
|
<td>{{ list.incomplete_count }}</td>
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load package_extras %}
|
{% load package_extras %}
|
||||||
{% load todolists %}
|
{% load todolists %}
|
||||||
|
{% load tz %}
|
||||||
|
{% load humanize %}
|
||||||
|
|
||||||
{% block title %}Arch Linux - Todo: {{ list.name }}{% endblock %}
|
{% block title %}Arch Linux - Todo: {{ list.name }}{% endblock %}
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</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">
|
<div class="todo-description">
|
||||||
{{list.stripped_description|default:'(no description)'|urlize|linebreaks}}
|
{{list.stripped_description|default:'(no description)'|urlize|linebreaks}}
|
||||||
@@ -103,7 +105,15 @@
|
|||||||
<span class="{{ pkg.status_css_class }}">{{ pkg.get_status_display }}</span>
|
<span class="{{ pkg.status_css_class }}">{{ pkg.get_status_display }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -28,7 +28,9 @@ class Migration(migrations.Migration):
|
|||||||
('created', models.DateTimeField(db_index=True)),
|
('created', models.DateTimeField(db_index=True)),
|
||||||
('last_modified', models.DateTimeField(editable=False)),
|
('last_modified', models.DateTimeField(editable=False)),
|
||||||
('raw', models.TextField(blank=True)),
|
('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={
|
options={
|
||||||
'get_latest_by': 'created',
|
'get_latest_by': 'created',
|
||||||
@@ -43,13 +45,16 @@ class Migration(migrations.Migration):
|
|||||||
('created', models.DateTimeField(editable=False)),
|
('created', models.DateTimeField(editable=False)),
|
||||||
('last_modified', models.DateTimeField(editable=False)),
|
('last_modified', models.DateTimeField(editable=False)),
|
||||||
('removed', models.DateTimeField(blank=True, null=True)),
|
('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)),
|
('comments', models.TextField(blank=True, null=True)),
|
||||||
('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')),
|
('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')),
|
('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')),
|
('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={
|
options={
|
||||||
'get_latest_by': 'created',
|
'get_latest_by': 'created',
|
||||||
|
@@ -13,6 +13,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='todolist',
|
model_name='todolist',
|
||||||
name='kind',
|
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)'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -23,7 +23,8 @@ class Todolist(models.Model):
|
|||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
|
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
|
||||||
created = models.DateTimeField(db_index=True)
|
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)
|
last_modified = models.DateTimeField(editable=False)
|
||||||
raw = models.TextField(blank=True)
|
raw = models.TextField(blank=True)
|
||||||
|
|
||||||
|
@@ -13,8 +13,9 @@ def todopkg_details_link(todopkg):
|
|||||||
pkg = todopkg.pkg
|
pkg = todopkg.pkg
|
||||||
if not pkg:
|
if not pkg:
|
||||||
return todopkg.pkgname
|
return todopkg.pkgname
|
||||||
link = '<a href="%s" title="View package details for %s">%s</a>'
|
link = '<a href={url}s" title="View package details for {pkgname}">{pkgname}</a>'
|
||||||
url = pkg_absolute_url(todopkg.repo, todopkg.arch, pkg.pkgname)
|
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:
|
# vim: set ts=4 sw=4 et:
|
||||||
|
Reference in New Issue
Block a user