41 Commits

Author SHA1 Message Date
49d616ef32 Merge branch 'upstream'
All checks were successful
Docker Image CI / build (push) Successful in 2m22s
Github-Actions / build (push) Successful in 53s
2025-04-12 14:38:43 -05:00
12f795bcf8 container names 2025-04-12 14:37:19 -05:00
Jelle van der Waa
653b482ec5 Include request scheme into opensearch data 2025-03-29 19:08:16 +01:00
Jelle van der Waa
ecc4bdf0a7 bump django to 5.0.13 2025-03-29 18:55:35 +01:00
luis.carilla
a45b88da4b fix linting issue 2025-03-29 15:13:22 +01:00
luis.carilla
e460ba4727 set a timeout for the rsync subprocess when checking mirror availability 2025-03-29 15:13:22 +01:00
Jelle van der Waa
910e428baa public: update to latest seqouia version
Closes: #553
2025-02-20 17:53:50 +01:00
8e6bc69713 Merge branch 'upstream'
All checks were successful
Github-Actions / build (push) Successful in 57s
Docker Image CI / build (push) Successful in 7m25s
2025-02-17 19:08:14 -05:00
Christian Heusel
f624f5677b donate: Add link to LoadView
This was forgotten in the previous agreement with that sponsor.

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 09:48:13 +01:00
7accacd5fd Merge pull request 'sync staging repos' (#8) from staging into master
All checks were successful
Github-Actions / build (push) Successful in 49s
Docker Image CI / build (push) Successful in 3m15s
Reviewed-on: #8
2025-01-14 22:26:44 +01:00
10fecc63e9 sync staging repos
All checks were successful
Github-Actions / build (push) Successful in 49s
Github-Actions / build (pull_request) Successful in 48s
Docker Image CI / build (pull_request) Successful in 2m35s
2025-01-14 16:21:04 -05:00
368e248efc Merge pull request 'fix(#6): lowercase repo names' (#7) from lowercase-repos into master
All checks were successful
Github-Actions / build (push) Successful in 48s
Docker Image CI / build (push) Successful in 2m38s
Reviewed-on: #7
2025-01-14 22:19:37 +01:00
a71b1f783e fix(#6): lowercase repo names
All checks were successful
Github-Actions / build (push) Successful in 50s
Github-Actions / build (pull_request) Successful in 48s
Docker Image CI / build (pull_request) Successful in 2m46s
closes #6
2025-01-14 16:08:51 -05:00
77531b1948 pull upstream
All checks were successful
Github-Actions / build (push) Successful in 47s
Docker Image CI / build (push) Successful in 3m24s
2025-01-11 17:44:36 -05:00
Jelle van der Waa
2bf2fa235f Update Python dependencies
Some checks failed
Github-Actions / build (push) Failing after 54s
2025-01-11 17:44:02 +01:00
Jakub Klinkovský
87a0d37953 Populate signoffs with a commit message of the tag in extra-testing, not the latest commit
Fixes #533
2025-01-11 17:35:32 +01:00
Jakub Klinkovský
4210a46f9a Add staging repos to fixtures
Without this we cannot populate the database with real staging packages,
which might be useful for testing something.
2025-01-11 17:35:32 +01:00
Jakub Klinkovský
bd4d50b84e Improve the "zebra" style of dynamic tables where rows can be dynamically hidden 2025-01-11 17:31:27 +01:00
nl6720
fcd473608c Use ISO 8601 date format
Use the `Y-m-d` format everywhere.

Fixes https://github.com/archlinux/archweb/issues/520
2025-01-11 17:21:05 +01:00
74bfaed558 Merge branch 'upstream'
Some checks failed
Github-Actions / build (push) Successful in 8m46s
Docker Image CI / build (push) Failing after 3m38s
2024-12-08 16:42:52 -05:00
dependabot[bot]
4e25b014e4 build(deps): bump django from 5.0.9 to 5.0.10
Bumps [django](https://github.com/django/django) from 5.0.9 to 5.0.10.
- [Commits](https://github.com/django/django/compare/5.0.9...5.0.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-07 11:32:14 +01:00
1a1d963ca4 use Artix org in Docker Hub
Some checks failed
Github-Actions / build (push) Successful in 8m11s
Docker Image CI / build (push) Failing after 1m51s
2024-10-22 22:09:19 -05:00
f8d5473c25 Merge branch 'upstream'
All checks were successful
Docker Image CI / build (push) Successful in 3m31s
Github-Actions / build (push) Successful in 9m0s
2024-10-09 10:56:22 -05:00
dependabot[bot]
c15b22203a build(deps): bump django from 5.0.8 to 5.0.9
Some checks failed
Github-Actions / build (push) Failing after 8s
Bumps [django](https://github.com/django/django) from 5.0.8 to 5.0.9.
- [Commits](https://github.com/django/django/compare/5.0.8...5.0.9)

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

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

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

Fixes https://github.com/archlinux/archweb/issues/520
2024-10-01 12:07:28 +02:00
61 changed files with 292 additions and 137 deletions

View File

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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -131,7 +131,7 @@ Archweb provides multiple management commands for importing various sorts of dat
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with. * 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.

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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():

View File

@@ -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

View File

@@ -1,20 +1,20 @@
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 main/fixtures/arches.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/repos.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 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
services: services:
packages_web: archweb_web:
container_name: artixweb-packages container_name: artixweb-packages
build: build:
context: ./ context: ./
@@ -25,7 +25,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 +35,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

View File

@@ -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

View File

@@ -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
}
} }
] ]

View File

@@ -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)

View File

@@ -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):
""" """

View File

@@ -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,7 +51,9 @@ 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&amp;fingerprint=on&amp;exact=on&amp;search=0x%s' % (scheme, pgp_server, key_id) url = '%s://%s/pks/lookup?op=vindex&amp;fingerprint=on&amp;exact=on&amp;search=0x%s' % (scheme,
pgp_server,
key_id)
if link_text is None: 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) values = (url, format_key(key_id), link_text)

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}
} }
] ]

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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')

View File

@@ -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

View File

@@ -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',

View File

@@ -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'),
), ),
] ]

View File

@@ -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

View File

@@ -3,6 +3,7 @@ from datetime import datetime
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,8 +93,9 @@ 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)

View File

@@ -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 = [

View File

@@ -1,5 +1,5 @@
-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.0.13
IPy==1.1 IPy==1.1
Markdown==3.3.7 Markdown==3.3.7
bencode.py==4.0.0 bencode.py==4.0.0
@@ -7,14 +7,14 @@ django-countries==7.6.1
django-extensions==3.2.3 django-extensions==3.2.3
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==3.8
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.3
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

View File

@@ -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
]

View File

@@ -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,7 +267,9 @@ 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']

View File

@@ -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;
} }

View File

@@ -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]);
@@ -330,8 +336,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]);

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -56,7 +56,7 @@
<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 %}

View File

@@ -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>

View File

@@ -84,6 +84,8 @@
<h3>Past donors</h3> <h3>Past donors</h3>
<p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> &amp; <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p>
<div id="donor-list"> <div id="donor-list">
<ul> <ul>
{% for donor in donors %} {% for donor in donors %}

View File

@@ -132,10 +132,10 @@
<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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -23,7 +23,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}}

View File

@@ -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',

View File

@@ -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)'),
), ),
] ]

View File

@@ -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)