Compare commits
	
		
			35 Commits
		
	
	
		
			74bfaed558
			...
			v25.4.12
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 49d616ef32 | |||
| 12f795bcf8 | |||
|   | 653b482ec5 | ||
|   | ecc4bdf0a7 | ||
|   | a45b88da4b | ||
|   | e460ba4727 | ||
|   | 910e428baa | ||
| 8e6bc69713 | |||
|   | f624f5677b | ||
|   | 2064099696 | ||
|   | 67209075c5 | ||
|   | 336d686ca2 | ||
|   | 4f0e24f1f7 | ||
| 7bf65b63ff | |||
| 188ead820d | |||
|   | e07054c8ea | ||
|   | 97aae09dce | ||
|   | 0ce1a0ea5f | ||
|   | f38770be76 | ||
|   | 2d39dc6379 | ||
|   | df4b0bfd67 | ||
|   | 5da7fa80c5 | ||
|   | 8d495d4fa7 | ||
|   | 796c3f410f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 37687bf9e4 | ||
| 7accacd5fd | |||
| 10fecc63e9 | |||
| 368e248efc | |||
| a71b1f783e | |||
| 77531b1948 | |||
|   | 2bf2fa235f | ||
|   | 87a0d37953 | ||
|   | 4210a46f9a | ||
|   | bd4d50b84e | ||
|   | fcd473608c | 
							
								
								
									
										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 | ||||||
|   | |||||||
							
								
								
									
										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: | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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,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 | ||||||
| @@ -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): | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -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&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) |     values = (url, format_key(key_id), link_text) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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) | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -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,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.10 | 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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  | ] | ||||||
|   | |||||||
| @@ -267,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'] | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -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]); | ||||||
| @@ -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]); | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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 %} | ||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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 %} | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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> & <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 %} | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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 %} | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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}} | ||||||
|   | |||||||
| @@ -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) | ||||||
| 
 | 
 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user