Compare commits
	
		
			63 Commits
		
	
	
		
			v24.8.10
			...
			9e36ad4e38
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9e36ad4e38 | |||
|   | 2027adaaba | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 05abc7f386 | ||
|   | c1d70301c9 | ||
|   | 323002ffe1 | ||
|   | a49a2cadc5 | ||
|   | a733efa173 | ||
|   | ce776a6297 | ||
|   | 7126b7b006 | ||
|   | ba4eb27ae3 | ||
|   | 375713717a | ||
| 93e59ab09b | |||
|   | 5e33be47a7 | ||
|   | 4473a9cdba | ||
| 49d616ef32 | |||
| 12f795bcf8 | |||
|   | 24b21bffed | ||
|   | 7f5d7e9082 | ||
|   | 653b482ec5 | ||
|   | ecc4bdf0a7 | ||
|   | a45b88da4b | ||
|   | e460ba4727 | ||
|   | 910e428baa | ||
| 8e6bc69713 | |||
|   | f624f5677b | ||
|   | 2064099696 | ||
|   | 67209075c5 | ||
|   | 336d686ca2 | ||
|   | 4f0e24f1f7 | ||
| 7bf65b63ff | |||
| 188ead820d | |||
|   | e07054c8ea | ||
|   | 97aae09dce | ||
|   | 0ce1a0ea5f | ||
|   | f38770be76 | ||
|   | 2d39dc6379 | ||
|   | df4b0bfd67 | ||
|   | 5da7fa80c5 | ||
|   | 8d495d4fa7 | ||
|   | 796c3f410f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 37687bf9e4 | ||
| 7accacd5fd | |||
| 10fecc63e9 | |||
| 368e248efc | |||
| a71b1f783e | |||
| 77531b1948 | |||
|   | 2bf2fa235f | ||
|   | 87a0d37953 | ||
|   | 4210a46f9a | ||
|   | bd4d50b84e | ||
|   | fcd473608c | ||
| 74bfaed558 | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 4e25b014e4 | ||
| 1a1d963ca4 | |||
| f8d5473c25 | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c15b22203a | ||
|   | b39c4c9385 | ||
| 43c333727d | |||
|   | 3da96a4007 | ||
| eda6dd9f3f | |||
|   | fc191bbe84 | ||
|   | c68204d6bb | ||
| 004064c15c | 
							
								
								
									
										2
									
								
								.flake8
									
									
									
									
									
								
							
							
						
						| @@ -1,3 +1,3 @@ | ||||
| [flake8] | ||||
| max-line-length = 300 | ||||
| max-line-length = 118 | ||||
| ignore = E731, E241, E741 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/build-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -22,7 +22,7 @@ jobs: | ||||
|       REGISTRY: gitea.artixlinux.org | ||||
|       DH_REGISTRY: docker.io | ||||
|       REPO_ORG: ${{ gitea.repository_owner }} | ||||
|       DH_ORG: corysanin | ||||
|       DH_ORG: artixlinux | ||||
|       IMAGE_NAME: archweb | ||||
|       DH_IMAGE_NAME: archweb | ||||
|       ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }} | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -9,14 +9,17 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v3 | ||||
|     - name: Set up Python 3.11 | ||||
|     - name: Set up Python 3.13 | ||||
|       uses: actions/setup-python@v4 | ||||
|       with: | ||||
|         python-version: 3.11 | ||||
|         python-version: 3.13 | ||||
|     - name: Install dependencies | ||||
|       run: | | ||||
|         python -m pip install --upgrade pip | ||||
|         pip install -r requirements.txt && pip install -r requirements_test.txt | ||||
|         pip install -r requirements.txt && pip install -r requirements_test.txt && pip install ruff | ||||
|     - name: Run ruff | ||||
|       run: | | ||||
|         ruff check . | ||||
|     - name: Lint with flake8 | ||||
|       run: | | ||||
|         make lint | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -131,7 +131,7 @@ Archweb provides multiple management commands for importing various sorts of dat | ||||
| * reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with. | ||||
| * donor_import - Import a single donator from a mail passed to stdin | ||||
| * mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details. | ||||
| * mirrorresolv - Poll every active mirror URLs and determine wheteher they have IP4 and/or IPv6 addresses. | ||||
| * mirrorresolv - Poll every active mirror URLs and determine whether they have IP4 and/or IPv6 addresses. | ||||
| * populate_signoffs - retrieves the latest commit message of a signoff-eligible package. | ||||
| * update_planet - Import all feeds for users who have a valid website and website_rss in their user profile. | ||||
| * read_links - Reads a repo.links.db.tar.gz file and updates the Soname model. | ||||
|   | ||||
| @@ -71,7 +71,8 @@ class Database(object): | ||||
|                         retry = False | ||||
|                     except OperationalError as exc: | ||||
|                         retry_count += 1 | ||||
|                         logger.error('Unable to update database \'%s\', retrying=%d', self.path, retry_count, exc_info=exc) | ||||
|                         logger.error('Unable to update database \'%s\', retrying=%d', | ||||
|                                      self.path, retry_count, exc_info=exc) | ||||
|                         time.sleep(5) | ||||
| 
 | ||||
|                 if retry_count == self.retry_limit: | ||||
|   | ||||
| @@ -59,7 +59,7 @@ class Command(BaseCommand): | ||||
|             arches = Arch.objects.filter(agnostic=False) | ||||
|             repos = Repo.objects.all() | ||||
| 
 | ||||
|         arch_path_map = {arch: None for arch in arches} | ||||
|         arch_path_map = dict.fromkeys(arches) | ||||
|         all_paths = set() | ||||
|         total_paths = 0 | ||||
|         for arch in arches: | ||||
| @@ -89,7 +89,9 @@ class Command(BaseCommand): | ||||
|         for name in all_paths: | ||||
|             manager.add_watch(name, mask) | ||||
| 
 | ||||
|         handler = EventHandler(arch_paths=arch_path_map, filename_suffix='.links.tar.gz', callback_func=wrapper_read_links) | ||||
|         handler = EventHandler(arch_paths=arch_path_map, | ||||
|                                filename_suffix='.links.tar.gz', | ||||
|                                callback_func=wrapper_read_links) | ||||
|         return pyinotify.Notifier(manager, handler) | ||||
| 
 | ||||
| 
 | ||||
|   | ||||
| @@ -539,7 +539,9 @@ def parse_info(pkgname, filename, iofile): | ||||
|         elif blockname: | ||||
|             store[blockname].append(line) | ||||
|         else: | ||||
|             raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname, filename, line)) | ||||
|             raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname, | ||||
|                                                                                                  filename, | ||||
|                                                                                                  line)) | ||||
|     return store | ||||
| 
 | ||||
| 
 | ||||
|   | ||||
| @@ -72,7 +72,7 @@ class Command(BaseCommand): | ||||
|             arches = Arch.objects.filter(agnostic=False) | ||||
|             repos = Repo.objects.all() | ||||
| 
 | ||||
|         arch_path_map = {arch: None for arch in arches} | ||||
|         arch_path_map = dict.fromkeys(arches) | ||||
|         all_paths = set() | ||||
|         total_paths = 0 | ||||
|         for arch in arches: | ||||
|   | ||||
							
								
								
									
										16
									
								
								devel/migrations/0011_userprofile_social.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | ||||
| from django.db import migrations, models | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('devel', '0010_merge_20230312_1527'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='userprofile', | ||||
|             name='social', | ||||
|             field=models.CharField(blank=True, max_length=200, null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -1,5 +1,6 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| import zoneinfo | ||||
| 
 | ||||
| from django.contrib.auth.models import Group, User | ||||
| from django.core.validators import MaxValueValidator, MinValueValidator | ||||
| from django.db import models | ||||
| @@ -39,6 +40,9 @@ class UserProfile(models.Model): | ||||
|     website = models.URLField(max_length=200, null=True, blank=True) | ||||
|     website_rss = models.URLField(max_length=200, null=True, blank=True, | ||||
|                                   help_text='RSS Feed of your website for planet.archlinux.org') | ||||
|     social = models.URLField(max_length=200, null=True, blank=True, | ||||
|                              verbose_name="Social account URL", | ||||
|                              help_text="Mastodon or Fediverse account URL") | ||||
|     yob = models.IntegerField("Year of birth", null=True, blank=True, | ||||
|                               validators=[MinValueValidator(1950), MaxValueValidator(2500)]) | ||||
|     country = CountryField(blank=True) | ||||
|   | ||||
| @@ -202,7 +202,8 @@ def non_existing_dependencies(packages): | ||||
| 
 | ||||
| 
 | ||||
| def non_reproducible_packages(packages): | ||||
|     statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname')) | ||||
|     statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, | ||||
|                                                                 pkg__pkgname__in=packages.values('pkgname')) | ||||
|     return linkify_non_reproducible_packages(statuses) | ||||
| 
 | ||||
| 
 | ||||
| @@ -227,7 +228,7 @@ def orphan_dependencies(packages): | ||||
|     JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase | ||||
|     JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname | ||||
|     ORDER BY child.pkgname; | ||||
|     """ | ||||
|     """  # noqa: E501 | ||||
|     cursor.execute(query) | ||||
| 
 | ||||
|     for row in cursor.fetchall(): | ||||
|   | ||||
| @@ -216,7 +216,9 @@ def tier0_mirror_auth(request): | ||||
|     token = credentials[1] | ||||
| 
 | ||||
|     groups = Group.objects.filter(name__in=SELECTED_GROUPS) | ||||
|     user = User.objects.filter(username=username, is_active=True, groups__in=groups).select_related('userprofile').first() | ||||
|     user = User.objects.filter(username=username, | ||||
|                                is_active=True, | ||||
|                                groups__in=groups).select_related('userprofile').first() | ||||
|     if not user: | ||||
|         return unauthorized | ||||
| 
 | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| version: '2' | ||||
|  | ||||
| # Run the following once: | ||||
| # docker compose run --rm packages_web python manage.py migrate | ||||
| # docker compose run --rm packages_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 packages_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local | ||||
| # docker compose run --rm archweb_web python manage.py migrate | ||||
| # docker compose run --rm archweb_web python manage.py loaddata main/fixtures/arches.json | ||||
| # docker compose run --rm archweb_web python manage.py loaddata main/fixtures/repos.json | ||||
| # docker compose run --rm archweb_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local | ||||
| ## go to /admin and create a user according to overlay/devel/fixtures/user_profiles.json | ||||
| ## go to /admin/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 packages_web python manage.py pgp_import ./config/keyring | ||||
| # docker compose run --rm archweb_web python manage.py generate_keyring pgp.surfnet.nl ./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/sites/site/1/change/ and set the domain | ||||
|  | ||||
|  | ||||
| services: | ||||
|     packages_web: | ||||
|     archweb_web: | ||||
|         container_name: artixweb-packages | ||||
|         build: | ||||
|             context: ./ | ||||
| @@ -25,7 +25,7 @@ services: | ||||
|         volumes: | ||||
|             - ./config:/usr/src/web/config | ||||
|      | ||||
|     packages_sync: | ||||
|     archweb_sync: | ||||
|         container_name: artixweb-sync | ||||
|         build: | ||||
|             context: ./ | ||||
| @@ -35,11 +35,11 @@ services: | ||||
|             - ./config:/usr/src/web/config | ||||
|         command: ./downloadpackages.sh | ||||
|      | ||||
|     packages_nginx: | ||||
|     archweb_nginx: | ||||
|         container_name: artixweb-nginx | ||||
|         image: linuxserver/nginx:latest | ||||
|         restart: "no" | ||||
|         ports: | ||||
|             - "8080:80" | ||||
|         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" | ||||
|  | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,18 @@ | ||||
|             "testing": true | ||||
|         } | ||||
|     },  | ||||
|     { | ||||
|         "pk": 15,  | ||||
|         "model": "main.repo",  | ||||
|         "fields": { | ||||
|             "bugs_category": 33,  | ||||
|             "staging": true,  | ||||
|             "name": "Extra-Staging", | ||||
|             "bugs_project": 5,  | ||||
|             "svn_root": "packages", | ||||
|             "testing": false | ||||
|         } | ||||
|     },  | ||||
|     { | ||||
|         "pk": 1,  | ||||
|         "model": "main.repo",  | ||||
| @@ -118,5 +130,17 @@ | ||||
|             "svn_root": "packages",  | ||||
|             "testing": true | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "pk": 16,  | ||||
|         "model": "main.repo",  | ||||
|         "fields": { | ||||
|             "bugs_category": 10,  | ||||
|             "staging": true,  | ||||
|             "name": "Core-Staging", | ||||
|             "bugs_project": 1,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": false | ||||
|         } | ||||
|     } | ||||
| ] | ||||
|   | ||||
| @@ -280,7 +280,8 @@ class Package(models.Model): | ||||
|             dep_pkgs = list(dep_pkgs) | ||||
|             dep = dep_pkgs[0] | ||||
|             if len(dep_pkgs) > 1: | ||||
|                 dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging] | ||||
|                 dep_pkgs = [d for d in dep_pkgs | ||||
|                             if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging] | ||||
|                 if len(dep_pkgs) > 0: | ||||
|                     dep = dep_pkgs[0] | ||||
|             trimmed.append(dep) | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import cssmin | ||||
| import jsmin | ||||
| from django.contrib.staticfiles.storage import ManifestStaticFilesStorage | ||||
| from django.core.files.base import ContentFile | ||||
| from django.utils.encoding import smart_str | ||||
| 
 | ||||
| import cssmin | ||||
| 
 | ||||
| 
 | ||||
| class MinifiedStaticFilesStorage(ManifestStaticFilesStorage): | ||||
|     """ | ||||
|   | ||||
| @@ -10,7 +10,7 @@ def format_key(key_id): | ||||
|     if len(key_id) in (8, 20): | ||||
|         return '0x%s' % key_id | ||||
|     elif len(key_id) == 40: | ||||
|         # normal display format is 5 groups of 4 hex chars seperated by spaces, | ||||
|         # normal display format is 5 groups of 4 hex chars separated by spaces, | ||||
|         # double space, then 5 more groups of 4 hex chars | ||||
|         split = tuple(key_id[i:i + 4] for i in range(0, 40, 4)) | ||||
|         return '%s\u00a0 %s' % (' '.join(split[0:5]), ' '.join(split[5:10])) | ||||
| @@ -35,7 +35,7 @@ def pgp_dev_key_link(key_id): | ||||
|     key_id = pad_key_id(key_id) | ||||
|     if not key_id: | ||||
|         return "Unknown" | ||||
|     link_text = (''.join((f'<span>{key_id[i:i+4]}</span>' for i in range(0, len(key_id), 4)))) | ||||
|     link_text = (''.join((f'<span>{key_id[i:i + 4]}</span>' for i in range(0, len(key_id), 4)))) | ||||
|     link_text = f'<div class="pgp-key-ids">{link_text}</div>' | ||||
|     return pgp_key_link(key_id, link_text) | ||||
| 
 | ||||
| @@ -51,7 +51,9 @@ def pgp_key_link(key_id, link_text=None): | ||||
|         return format_key(key_id) | ||||
|     pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False) | ||||
|     scheme = 'https' if pgp_server_secure else 'http' | ||||
|     url = '%s://%s/pks/lookup?op=vindex&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: | ||||
|         link_text = '0x%s' % key_id[-8:] | ||||
|     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: | ||||
|             if logger.isEnabledFor(logging.DEBUG): | ||||
|                 logger.debug("rsync cmd: %s", ' '.join(rsync_cmd)) | ||||
| 
 | ||||
|             start = time.time() | ||||
|             proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE) | ||||
|             _, errdata = proc.communicate() | ||||
|             end = time.time() | ||||
|         log.duration = end - start | ||||
|         if proc.returncode != 0: | ||||
|             timeout_expired = False | ||||
|             # add an arbitrary 5-second buffer to ensure the process completes and to catch actual rsync timeouts. | ||||
|             rsync_subprocess_timeout = timeout + 5 | ||||
|             try: | ||||
|                 proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE) | ||||
|                 _, errdata = proc.communicate(timeout=rsync_subprocess_timeout) | ||||
| 
 | ||||
|                 end = time.time() | ||||
|                 log.duration = end - start | ||||
|             except subprocess.TimeoutExpired: | ||||
|                 timeout_expired = True | ||||
|                 proc.kill() | ||||
|                 logger.debug("rsync command timeout error: %s, %s", url, errdata) | ||||
|                 log.is_success = False | ||||
|                 log.duration = None | ||||
|                 log.error = f"rsync subprocess killed after {rsync_subprocess_timeout} seconds" | ||||
| 
 | ||||
|         if proc.returncode != 0 and not timeout_expired: | ||||
|             logger.debug("error: %s, %s", url, errdata) | ||||
|             log.is_success = False | ||||
|             log.error = errdata.strip().decode('utf-8') | ||||
| @@ -197,7 +211,7 @@ def check_rsync_url(mirror_url, location, timeout): | ||||
|             # don't record a duration as it is misleading | ||||
|             if proc.returncode in (1, 30, 35): | ||||
|                 log.duration = None | ||||
|         else: | ||||
|         elif not timeout_expired: | ||||
|             logger.debug("success: %s, %.2f", url, log.duration) | ||||
|             if os.path.exists(lastsync_path): | ||||
|                 with open(lastsync_path, 'r') as lastsync: | ||||
|   | ||||
| @@ -27,7 +27,7 @@ def test_mirrorurl_get_full_url(mirrorurl): | ||||
| 
 | ||||
| def test_mirror_url_clean(mirrorurl): | ||||
|     mirrorurl.clean() | ||||
|     # TOOD(jelle): this expects HOSTNAME to resolve, maybe mock | ||||
|     # TODO(jelle): this expects HOSTNAME to resolve, maybe mock | ||||
|     assert mirrorurl.has_ipv4 | ||||
|     # requires ipv6 on host... mock? | ||||
|     # assert mirrorurl.has_ipv6 == True | ||||
|   | ||||
| @@ -54,10 +54,12 @@ class NewsCreateView(CreateView): | ||||
|             if settings.MAILMAN_PASSWORD: | ||||
|                 headers['Approved'] = settings.MAILMAN_PASSWORD | ||||
|             template = loader.get_template('news/news_email_notification.txt') | ||||
|             author = newsitem.author.get_full_name() | ||||
|             from_ = f'"Arch Linux: Recent news updates: {author}" <{settings.ANNOUNCE_EMAIL}>' | ||||
|             EmailMessage( | ||||
|                 subject=f'[arch-announce] {newsitem.title}', | ||||
|                 body=template.render(ctx), | ||||
|                 from_email=f'"Arch Linux: Recent news updates: {newsitem.author.get_full_name()}" <{settings.ANNOUNCE_EMAIL}>', | ||||
|                 from_email=from_, | ||||
|                 to=[settings.ANNOUNCE_EMAIL], | ||||
|                 headers=headers).send() | ||||
|         return super(NewsCreateView, self).form_valid(form) | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "World-Gremlins", | ||||
|             "name": "world-gremlins", | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages", | ||||
|             "testing": true | ||||
| @@ -17,7 +17,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "Galaxy-Gremlins", | ||||
|             "name": "galaxy-gremlins", | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages", | ||||
|             "testing": true | ||||
| @@ -29,7 +29,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "System",  | ||||
|             "name": "system",  | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": false | ||||
| @@ -41,7 +41,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "World",  | ||||
|             "name": "world",  | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": false | ||||
| @@ -53,7 +53,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "Galaxy",  | ||||
|             "name": "galaxy",  | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": false | ||||
| @@ -65,7 +65,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "Lib32",  | ||||
|             "name": "lib32",  | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": false | ||||
| @@ -77,7 +77,7 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "Lib32-Gremlins",  | ||||
|             "name": "lib32-gremlins",  | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "testing": true | ||||
| @@ -89,10 +89,58 @@ | ||||
|         "fields": { | ||||
|             "bugs_category": 0,  | ||||
|             "staging": false,  | ||||
|             "name": "System-Gremlins", | ||||
|             "name": "system-gremlins", | ||||
|             "bugs_project": 0,  | ||||
|             "svn_root": "packages",  | ||||
|             "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 | ||||
| 
 | ||||
| 
 | ||||
| def get_last_log(repo, pkgbase): | ||||
|     # Gitlab requires the path to the gitlab repo to be html encoded and project name encoded in a different special way | ||||
| def get_tag_info(repo, pkgbase, version): | ||||
|     # Gitlab requires the path to the gitlab repo to be html encoded and | ||||
|     # project name encoded in a different special way | ||||
|     pkgrepo = urllib.parse.quote_plus(f'{settings.GITLAB_PACKAGE_REPO}/') + gitlab_project_name_to_path(pkgbase) | ||||
|     url = f'https://{settings.GITLAB_INSTANCE}/api/v4/projects/{pkgrepo}/repository/tags' | ||||
| 
 | ||||
| @@ -65,8 +66,12 @@ def get_last_log(repo, pkgbase): | ||||
|         return None | ||||
| 
 | ||||
|     tags = r.json() | ||||
| 
 | ||||
|     # filter out unrelated tags | ||||
|     tags = [tag for tag in tags if tag["name"] == version] | ||||
| 
 | ||||
|     if len(tags) == 0: | ||||
|         logger.error("No tags found for pkgbase %s (%s)", pkgbase, repo) | ||||
|         logger.error("No tags found for pkgbase %s (%s) version %s", pkgbase, repo, version) | ||||
|         return None | ||||
| 
 | ||||
|     tag = tags[0] | ||||
| @@ -89,7 +94,7 @@ def add_signoff_comments(): | ||||
|         if not group.default_spec: | ||||
|             continue | ||||
| 
 | ||||
|         log = get_last_log(group.repo, group.pkgbase) | ||||
|         log = get_tag_info(group.repo, group.pkgbase, group.version) | ||||
|         if log is None: | ||||
|             continue | ||||
| 
 | ||||
| @@ -108,7 +113,8 @@ def cleanup_signoff_comments(): | ||||
|     id_signoffs = [signoff.id for g in groups for signoff in g.signoffs] | ||||
|     logger.info("Keeping %s signoffs", len(id_signoffs)) | ||||
|     # FakeSignoffSpecification's have no id | ||||
|     id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification, FakeSignoffSpecification)] | ||||
|     id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification, | ||||
|                                                                             FakeSignoffSpecification)] | ||||
|     logger.info("Keeping %s signoffspecifications", len(id_signoffspecs)) | ||||
| 
 | ||||
|     Signoff.objects.exclude(id__in=id_signoffs).delete() | ||||
|   | ||||
| @@ -253,7 +253,8 @@ class UpdateManager(models.Manager): | ||||
|             if new_pkg: | ||||
|                 update.action_flag = CHANGE | ||||
|                 # ensure we should even be logging this | ||||
|                 if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel and old_pkg.epoch == new_pkg.epoch: | ||||
|                 if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel \ | ||||
|                    and old_pkg.epoch == new_pkg.epoch: | ||||
|                     # all relevant fields were the same; e.g. a force update | ||||
|                     return | ||||
|             else: | ||||
| @@ -395,7 +396,8 @@ class RelatedToBase(models.Model): | ||||
|         # actually satisfy the requirements | ||||
|         if self.comparison and self.version: | ||||
|             alpm = AlpmAPI() | ||||
|             pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version, self.comparison, self.version)] | ||||
|             pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version, | ||||
|                                                                                        self.comparison, self.version)] | ||||
|         if len(pkgs) == 0: | ||||
|             # couldn't find a package in the DB | ||||
|             # it should be a virtual depend (or a removed package) | ||||
|   | ||||
| @@ -26,9 +26,9 @@ class RematchDeveloperTest(TransactionTestCase): | ||||
|         self.package.delete() | ||||
| 
 | ||||
|     def test_basic(self): | ||||
|         with mock.patch('packages.management.commands.populate_signoffs.get_last_log') as get_last_log: | ||||
|         with mock.patch('packages.management.commands.populate_signoffs.get_tag_info') as get_tag_info: | ||||
|             comment = 'upgpkg: 0.1-1: rebuild' | ||||
|             get_last_log.return_value = {'message': f'{comment}\n', 'author': 'foo@archlinux.org'} | ||||
|             get_tag_info.return_value = {'message': f'{comment}\n', 'author': 'foo@archlinux.org'} | ||||
|             call_command('populate_signoffs') | ||||
| 
 | ||||
|             signoff_spec = SignoffSpecification.objects.first() | ||||
| @@ -36,8 +36,8 @@ class RematchDeveloperTest(TransactionTestCase): | ||||
|             assert signoff_spec.pkgbase == self.package.pkgbase | ||||
| 
 | ||||
|     def test_invalid(self): | ||||
|         with mock.patch('packages.management.commands.populate_signoffs.get_last_log') as get_last_log: | ||||
|             get_last_log.return_value = None | ||||
|         with mock.patch('packages.management.commands.populate_signoffs.get_tag_info') as get_tag_info: | ||||
|             get_tag_info.return_value = None | ||||
|             call_command('populate_signoffs') | ||||
| 
 | ||||
|             assert SignoffSpecification.objects.count() == 0 | ||||
|   | ||||
| @@ -67,6 +67,7 @@ def test_sort(client, package): | ||||
| def test_packages(client, package): | ||||
|     response = client.get('/opensearch/packages/') | ||||
|     assert response.status_code == 200 | ||||
|     assert 'template="http://example.com/opensearch/packages/"' in response.content.decode() | ||||
| 
 | ||||
| 
 | ||||
| def test_packages_suggest(client, package): | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from collections import defaultdict | ||||
| from django.contrib import messages | ||||
| from django.contrib.auth.decorators import permission_required | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.sites.models import Site | ||||
| from django.core.cache import cache | ||||
| from django.db.models import Q | ||||
| from django.http import HttpResponse, HttpResponseBadRequest | ||||
| @@ -21,10 +22,10 @@ from ..utils import get_wrong_permissions, multilib_differences | ||||
| @require_safe | ||||
| @cache_control(public=True, max_age=86400) | ||||
| def opensearch(request): | ||||
|     domain = "%s://%s" % ('https', request.META.get('HTTP_HOST')) | ||||
|     current_site = Site.objects.get_current() | ||||
| 
 | ||||
|     return render(request, 'packages/opensearch.xml', | ||||
|                   {'domain': domain}, | ||||
|                   {'domain': f'{request.scheme}://{current_site.domain}'}, | ||||
|                   content_type='application/opensearchdescription+xml') | ||||
| 
 | ||||
| 
 | ||||
| @@ -140,8 +141,14 @@ def sonames(request): | ||||
|         name = request.GET.get('name') | ||||
| 
 | ||||
|         if name: | ||||
|             sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname', 'pkg__pkgver', 'pkg__pkgrel', 'pkg__epoch', 'pkg__repo__name') | ||||
|             packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'], 'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'], 'repo': soname['pkg__repo__name'].lower()} for soname in sonames] | ||||
|             sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname', | ||||
|                                                                           'pkg__pkgver', | ||||
|                                                                           'pkg__pkgrel', | ||||
|                                                                           'pkg__epoch', | ||||
|                                                                           'pkg__repo__name') | ||||
|             packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'], | ||||
|                          'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'], | ||||
|                          'repo': soname['pkg__repo__name'].lower()} for soname in sonames] | ||||
|         else: | ||||
|             return HttpResponseBadRequest('name parameter is required') | ||||
| 
 | ||||
|   | ||||
| @@ -38,7 +38,8 @@ class FlagForm(forms.Form): | ||||
|         # make sure the message isn't garbage (only punctuation or whitespace) | ||||
|         # or spam (using a simple denylist) | ||||
|         # and ensure a certain minimum length | ||||
|         if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) or len(data) < 3: | ||||
|         if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) \ | ||||
|            or len(data) < 3: | ||||
|             raise forms.ValidationError("Enter a valid and useful out-of-date message.") | ||||
|         return data | ||||
| 
 | ||||
|   | ||||
| @@ -50,7 +50,8 @@ class Migration(migrations.Migration): | ||||
|                 ('author', models.CharField(max_length=255)), | ||||
|                 ('publishdate', models.DateTimeField(db_index=True, verbose_name='publish date')), | ||||
|                 ('url', models.CharField(max_length=255, verbose_name='URL')), | ||||
|                 ('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='feed', to='planet.Feed')), | ||||
|                 ('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, | ||||
|                                            related_name='feed', to='planet.Feed')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name_plural': 'Feed Items', | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class Migration(migrations.Migration): | ||||
|         migrations.AlterField( | ||||
|             model_name='feeditem', | ||||
|             name='feed', | ||||
|             field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='planet.feed'), | ||||
|             field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, | ||||
|                                     related_name='items', to='planet.feed'), | ||||
|         ), | ||||
|     ] | ||||
|   | ||||
| @@ -8,7 +8,7 @@ fi | ||||
|  | ||||
| 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 | ||||
| do | ||||
|   | ||||
| @@ -3,6 +3,7 @@ from datetime import datetime | ||||
| from operator import attrgetter | ||||
| 
 | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.sites.models import Site | ||||
| from django.db.models import Count, Q | ||||
| from django.http import HttpResponse | ||||
| from django.shortcuts import get_object_or_404, render | ||||
| @@ -25,12 +26,12 @@ def index(request): | ||||
|     else: | ||||
|         def updates(): | ||||
|             return get_recent_updates() | ||||
|     domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) | ||||
|     current_site = Site.objects.get_current() | ||||
|     context = { | ||||
|         'news_updates': News.objects.order_by('-postdate', '-id')[:15], | ||||
|         'pkg_updates': updates, | ||||
|         'staff_groups': StaffGroup.objects.all(), | ||||
|         'domain': domain, | ||||
|         'domain': f'{request.scheme}://{current_site.domain}', | ||||
|     } | ||||
|     return render(request, 'public/index.html', context) | ||||
| 
 | ||||
| @@ -92,8 +93,9 @@ def feeds(request): | ||||
| @cache_control(max_age=307) | ||||
| def keys(request): | ||||
|     profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id') | ||||
|     users = User.objects.filter( | ||||
|             is_active=True, userprofile__id__in=profile_ids).order_by('first_name', 'last_name').select_related('userprofile') | ||||
|     users = User.objects.filter(is_active=True, | ||||
|                                 userprofile__id__in=profile_ids).order_by('first_name', | ||||
|                                                                           'last_name').select_related('userprofile') | ||||
|     user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users | ||||
|             if user.userprofile.pgp_key) | ||||
| 
 | ||||
|   | ||||
| @@ -9,8 +9,10 @@ from .views import ReleaseDetailView, ReleaseListView | ||||
| releases_patterns = [ | ||||
|     path('', ReleaseListView.as_view(), name='releng-release-list'), | ||||
|     path('json/', views.releases_json, name='releng-release-list-json'), | ||||
|     re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()), name='releng-release-detail'), | ||||
|     re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), name='releng-release-torrent'), | ||||
|     re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()), | ||||
|             name='releng-release-detail'), | ||||
|     re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), | ||||
|             name='releng-release-torrent'), | ||||
| ] | ||||
| 
 | ||||
| netboot_patterns = [ | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| -e git+https://github.com/fredj/cssmin.git@master#egg=cssmin | ||||
| Django==5.0.8 | ||||
| Django==5.1.9 | ||||
| IPy==1.1 | ||||
| Markdown==3.3.7 | ||||
| bencode.py==4.0.0 | ||||
| django-countries==7.6.1 | ||||
| django-extensions==3.2.3 | ||||
| django-extensions==4.1 | ||||
| jsmin==3.0.1 | ||||
| pgpdump==1.5 | ||||
| parse==1.19.0 | ||||
| parse==1.20.2 | ||||
| sqlparse==0.5.0 | ||||
| django-csp==3.7 | ||||
| django-csp==4.0 | ||||
| ptpython==2.0.4 | ||||
| feedparser==6.0.10 | ||||
| feedparser==6.0.11 | ||||
| bleach==6.0.0 | ||||
| requests==2.32.0 | ||||
| xtarfile==0.1.0 | ||||
| zstandard==0.17.0 | ||||
| whitenoise==6.7.0 | ||||
| requests==2.32.4 | ||||
| xtarfile==0.2.1 | ||||
| zstandard==0.23.0 | ||||
| whitenoise==6.8.2 | ||||
| django-prometheus==2.3.1 | ||||
|   | ||||
| @@ -24,7 +24,6 @@ select = [ | ||||
| ] | ||||
|  | ||||
| ignore = [ | ||||
|     "E501", # line lengt violation | ||||
|     "E731", # Do not assign a `lambda` expression, use a `def` | ||||
|     "B904", # Within an `except` clause, raise exceptions with `raise ... from err` | ||||
|     "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` | ||||
| @@ -34,3 +33,8 @@ ignore = [ | ||||
|     "DJ008", # Model does not define `__str__` method | ||||
|     "DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url` | ||||
| ] | ||||
|  | ||||
| exclude = [ | ||||
|     "*/migrations/*.py",  # Ignore Django migrations | ||||
|     "src/cssmin/*"  # cssmin, not our code | ||||
| ] | ||||
|   | ||||
| @@ -41,9 +41,6 @@ SITE_ID = 1 | ||||
| DATE_FORMAT = 'Y-m-d' | ||||
| DATETIME_FORMAT = 'Y-m-d H:i' | ||||
| 
 | ||||
| # Disable so our own DATE_FORMAT/DATETIME_FORMAT is used. | ||||
| USE_L10N = False | ||||
| 
 | ||||
| # Login URL configuration | ||||
| LOGIN_URL = '/login/' | ||||
| LOGIN_REDIRECT_URL = '/' | ||||
| @@ -270,7 +267,9 @@ if DEBUG_TOOLBAR: | ||||
|     INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar'] | ||||
| 
 | ||||
| if PROMETHEUS_METRICS: | ||||
|     MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', *list(MIDDLEWARE), 'django_prometheus.middleware.PrometheusAfterMiddleware'] | ||||
|     MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', | ||||
|                   *list(MIDDLEWARE), | ||||
|                   'django_prometheus.middleware.PrometheusAfterMiddleware'] | ||||
| 
 | ||||
|     INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus'] | ||||
| 
 | ||||
|   | ||||
| @@ -754,6 +754,10 @@ table.results { | ||||
|         text-align: center; | ||||
|     } | ||||
|  | ||||
|     .results [hidden] { | ||||
|         display: none; | ||||
|     } | ||||
|  | ||||
| /* pkglist: layout */ | ||||
| #pkglist-about { | ||||
|     margin-top: 1.5em; | ||||
| @@ -1056,12 +1060,12 @@ table td.country { | ||||
|     background: #ffd; | ||||
| } | ||||
|  | ||||
| .results tr:nth-child(even), | ||||
| .results tr:nth-child(even of :not([hidden])), | ||||
| #article-list tr:nth-child(even) { | ||||
|     background: #e4eeff; | ||||
| } | ||||
|  | ||||
| .results tr:nth-child(odd), | ||||
| .results tr:nth-child(odd of :not([hidden])), | ||||
| #article-list tr:nth-child(odd) { | ||||
|     background: #fff; | ||||
| } | ||||
|   | ||||
| @@ -212,8 +212,14 @@ function filter_pkgs_list(filter_ele, tbody_ele) { | ||||
|         rows = rows.has('.incomplete'); | ||||
|     } | ||||
|     /* hide all rows, then show the set we care about */ | ||||
|     all_rows.hide(); | ||||
|     rows.show(); | ||||
|     // note that we don't use .hide() from jQuery because it adds display:none | ||||
|     // which is very expensive to query in CSS ([style*="display: none"]) | ||||
|     all_rows.each(function() { | ||||
|         $(this).attr('hidden', true); | ||||
|     }); | ||||
|     rows.each(function() { | ||||
|         $(this).removeAttr('hidden'); | ||||
|     }); | ||||
|     $('#filter-count').text(rows.length); | ||||
|     /* make sure we update the odd/even styling from sorting */ | ||||
|     $('.results').trigger('applyWidgets', [false]); | ||||
| @@ -318,6 +324,10 @@ function filter_signoffs() { | ||||
|     /* start with all rows, and then remove ones we shouldn't show */ | ||||
|     var rows = $('#tbody_signoffs').children(), | ||||
|         all_rows = rows; | ||||
|     /* apply the filters, cheaper ones first */ | ||||
|     if ($('#id_mine_only').is(':checked')) { | ||||
|         rows = rows.filter('.mine'); | ||||
|     } | ||||
|     /* apply arch and repo filters */ | ||||
|     $('#signoffs_filter .arch_filter').add( | ||||
|             '#signoffs_filter .repo_filter').each(function() { | ||||
| @@ -330,8 +340,14 @@ function filter_signoffs() { | ||||
|         rows = rows.has('td.signoff-no'); | ||||
|     } | ||||
|     /* hide all rows, then show the set we care about */ | ||||
|     all_rows.hide(); | ||||
|     rows.show(); | ||||
|     // note that we don't use .hide() from jQuery because it adds display:none | ||||
|     // which is very expensive to query in CSS ([style*="display: none"]) | ||||
|     all_rows.each(function() { | ||||
|         $(this).attr('hidden', true); | ||||
|     }); | ||||
|     rows.each(function() { | ||||
|         $(this).removeAttr('hidden'); | ||||
|     }); | ||||
|     $('#filter-count').text(rows.length); | ||||
|     /* make sure we update the odd/even styling from sorting */ | ||||
|     $('.results').trigger('applyWidgets', [false]); | ||||
| @@ -340,6 +356,7 @@ function filter_signoffs() { | ||||
| function filter_signoffs_reset() { | ||||
|     $('#signoffs_filter .arch_filter').prop('checked', true); | ||||
|     $('#signoffs_filter .repo_filter').prop('checked', true); | ||||
|     $('#id_mine_only').prop('checked', false); | ||||
|     $('#id_pending').prop('checked', false); | ||||
|     filter_signoffs(); | ||||
| } | ||||
|   | ||||
| Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.2 KiB | 
| Before Width: | Height: | Size: 6.3 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 3.7 KiB | 
| @@ -30,7 +30,7 @@ | ||||
|     <tbody> | ||||
|         {% for entry in admin_log %} | ||||
|         <tr> | ||||
|             <th scope="row">{{ entry.action_time|date:"DATETIME_FORMAT" }}</th> | ||||
|             <th scope="row">{{ entry.action_time|date:"Y-m-d H:i" }}</th> | ||||
|             {% if log_user %} | ||||
|             <td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td> | ||||
|             {% else %} | ||||
|   | ||||
| @@ -36,8 +36,8 @@ | ||||
|                         {% endif %}{% endwith %}</td> | ||||
|                     <td>{{ pkg.repo.name }}</td> | ||||
|                     <td>{{ pkg.arch.name }}</td> | ||||
|                     <td>{{ pkg.flag_date|date }}</td> | ||||
|                     <td>{{ pkg.last_update|date }}</td> | ||||
|                     <td>{{ pkg.flag_date|date:"Y-m-d" }}</td> | ||||
|                     <td>{{ pkg.last_update|date:"Y-m-d" }}</td> | ||||
|                 </tr> | ||||
|             {% empty %} | ||||
|                 <tr class="empty"><td colspan="7"><em>No flagged packages to display</em></td></tr> | ||||
| @@ -68,7 +68,7 @@ | ||||
|                 <td>{{ group.version }}</td> | ||||
|                 <td>{{ group.arch.name }}</td> | ||||
|                 <td>{{ group.target_repo }}</td> | ||||
|                 <td>{{ group.last_update|date }}</td> | ||||
|                 <td>{{ group.last_update|date:"Y-m-d" }}</td> | ||||
|                 {% if group.specification.known_bad %} | ||||
|                 <td class="approval signoff-bad">Bad</td> | ||||
|                 {% else %} | ||||
| @@ -138,7 +138,7 @@ | ||||
|             <tr> | ||||
|                 <td class="wrap"><a href="{{ todo.get_absolute_url }}" | ||||
|                         title="View todo list: {{ todo.name }}">{{ todo.name }}</a></td> | ||||
|                 <td>{{ todo.created|date }}</td> | ||||
|                 <td>{{ todo.created|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ todo.creator.get_full_name }}</td> | ||||
|                 <td>{{ todo.pkg_count }}</td> | ||||
|                 <td>{{ todo.incomplete_count }}</td> | ||||
|   | ||||
| @@ -60,9 +60,9 @@ | ||||
|                 {% else %} | ||||
|                 <td>{{ pkg.full_version }}</td> | ||||
|                 {% endif %} | ||||
|                 <td>{{ pkg.last_update|date }}</td> | ||||
|                 <td>{{ pkg.build_date|date }}</td> | ||||
|                 <td>{{ pkg.flag_date|date }}</td> | ||||
|                 <td>{{ pkg.last_update|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ pkg.build_date|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ pkg.flag_date|date:"Y-m-d" }}</td> | ||||
|                 {% for attr in column_attrs %} | ||||
|                 <td>{{ pkg|attribute:attr }}</td> | ||||
|                 {% endfor %} | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| <div class="box"> | ||||
| 
 | ||||
|     <h2>Tier 0 Mirror usage information</h2> | ||||
|     <p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with a HTTP Basic Auth password unique per Staff member.</p> | ||||
|     <p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with an HTTP Basic Auth password unique per Staff member.</p> | ||||
|     {% if mirror_url %} | ||||
|       <code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button> | ||||
| 
 | ||||
|   | ||||
| @@ -33,7 +33,7 @@ | ||||
|         <tbody> | ||||
|             {% for item in news_list %} | ||||
|             <tr> | ||||
|                 <td>{{ item.postdate|date }}</td> | ||||
|                 <td>{{ item.postdate|date:"Y-m-d" }}</td> | ||||
|                 <td class="wrap"><a href="{{ item.get_absolute_url }}" | ||||
|                         title="View: {{ item.title }}">{{ item.title }}</a></td> | ||||
|                 <td>{{ item.author.get_full_name }}</td> | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|     </ul> | ||||
|     {% endif %} | ||||
| 
 | ||||
|     <p class="article-info">{{ news.postdate|date }} - {{ news.author.get_full_name }}</p> | ||||
|     <p class="article-info">{{ news.postdate|date:"Y-m-d" }} - {{ news.author.get_full_name }}</p> | ||||
| 
 | ||||
|     <div class="article-content" itemprop="articleBody">{{ news.html }}</div> | ||||
| </div> | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|                 <th>Multilib Version</th> | ||||
|                 <th>x86_64 Version</th> | ||||
|                 <th>x86_64 Name</th> | ||||
|                 <th>x864_ Repo</th> | ||||
|                 <th>x86_64 Repo</th> | ||||
|                 <th>Multilib Last Updated</th> | ||||
|                 <th>x86_64 Last Updated</th> | ||||
|             </tr> | ||||
| @@ -29,8 +29,8 @@ | ||||
|                 <td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td> | ||||
|                 <td>{% pkg_details_link pkg2 %}</td> | ||||
|                 <td>{{ pkg2.repo }}</td> | ||||
|                 <td>{{ pkg1.last_update|date }}</td> | ||||
|                 <td>{{ pkg2.last_update|date }}</td> | ||||
|                 <td>{{ pkg1.last_update|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ pkg2.last_update|date:"Y-m-d" }}</td> | ||||
|             </tr> | ||||
|             {% endfor %} | ||||
|         </tbody> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| {% extends "base.html" %} | ||||
| {% load package_extras %} | ||||
| {% load humanize %} | ||||
| {% load details_link %} | ||||
| 
 | ||||
| {% block title %}Arch Linux - Flag Package - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %} | ||||
| {% block head %}<meta name="robots" content="noindex"/>{% endblock %} | ||||
| @@ -29,9 +30,9 @@ | ||||
|     with your additional text.</p> | ||||
| 
 | ||||
|     <p><strong>Note:</strong> Do <em>not</em> use this facility if the | ||||
|     package is broken! The package will be unflagged and the report will be ignored! | ||||
|     <a href="https://bugs.archlinux.org/" title="Arch Linux Bugtracker">Use the | ||||
|     bugtracker to file a bug</a> instead.</p> | ||||
|     package is broken! The package will be unflagged and the report will be ignored! File an issue on | ||||
|     <a href="{% bugs_list package %}" title="Bug tickets for {{ package }}">the package's GitLab repository</a> | ||||
|     instead.</p> | ||||
| 
 | ||||
|     <p>Please confirm your flag request for {{package.pkgname}}:</p> | ||||
| 
 | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|                 <td><a href="/groups/{{ grp.arch }}/{{ grp.name }}/" | ||||
|                         title="Group details for {{ grp.name }}">{{ grp.name }}</a></td> | ||||
|                 <td>{{ grp.count }}</td> | ||||
|                 <td>{{ grp.last_update|date }}</td> | ||||
|                 <td>{{ grp.last_update|date:"Y-m-d" }}</td> | ||||
|             </tr> | ||||
|             {% endfor %} | ||||
|         </tbody> | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
| 		</li> | ||||
| 		{% endif %} | ||||
|                 {% if pkg.flag_date %} | ||||
|                 <li><span class="flagged">Flagged out-of-date on {{ pkg.flag_date|date }}</span></li> | ||||
|                 <li><span class="flagged">Flagged out-of-date on {{ pkg.flag_date|date:"Y-m-d" }}</span></li> | ||||
|                 {% with tp=pkg.in_testing %}{% if tp %} | ||||
|                 <li><span class="flagged">Version | ||||
|                     <a href="{{ tp.get_absolute_url }}" | ||||
| @@ -98,8 +98,8 @@ | ||||
|                     title="Browse packages for {{ pkg.arch.name }} architecture">{{ pkg.arch.name }}</a></td> | ||||
|         </tr><tr> | ||||
|             <th>Repository:</th> | ||||
|             <td><a href="/packages/?repo={{ pkg.repo.name|capfirst }}" | ||||
|                     title="Browse the {{ pkg.repo.name|capfirst }} repository">{{ pkg.repo.name|capfirst }}</a></td> | ||||
|             <td><a href="/packages/?repo={{ pkg.repo.name }}" | ||||
|                     title="Browse the {{ pkg.repo.name }} repository">{{ pkg.repo.name }}</a></td> | ||||
|         </tr> | ||||
|         {% if pkg.pkgname == pkg.pkgbase %} | ||||
|         {% with splits=pkg.split_packages %}{% if splits %} | ||||
| @@ -123,7 +123,7 @@ | ||||
|             <th>Description:</th> | ||||
|             <td class="wrap" itemprop="description">{{ pkg.pkgdesc|default:"" }}</td> | ||||
|         </tr><tr> | ||||
|             <th>Upstream URL:</th> | ||||
|             <th>Homepage:</th> | ||||
|             <td>{% if pkg.url %}<a itemprop="url" href="{{ pkg.url }}" | ||||
|                     title="Visit the website for {{ pkg.pkgname }}">{{ pkg.url|url_unquote }}</a>{% endif %}</td> | ||||
|         </tr><tr> | ||||
| @@ -188,19 +188,19 @@ | ||||
|             {% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td> | ||||
|         </tr><tr> | ||||
|             <th>Build Date:</th> | ||||
|             <td>{{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC</td> | ||||
|             <td>{{ pkg.build_date|date:"Y-m-d H:i" }} UTC</td> | ||||
|         </tr>{% if pkg.signature %}<tr> | ||||
|             <th>Signed By:</th> | ||||
|             <td>{% with signer=pkg.signer %}{% if signer %}{% pgp_key_link pkg.signature.key_id signer.get_full_name|safe %}{% else %}Unknown ({% pgp_key_link pkg.signature.key_id|safe %}){% endif %}{% endwith %}</td> | ||||
|         </tr><tr> | ||||
|             <th>Signature Date:</th> | ||||
|             <td>{{ pkg.signature.creation_time|date:"DATETIME_FORMAT" }} UTC</td> | ||||
|             <td>{{ pkg.signature.creation_time|date:"Y-m-d H:i" }} UTC</td> | ||||
|         </tr>{% else %}<tr> | ||||
|             <th>Signed By:</th> | ||||
|             <td>Unsigned</td> | ||||
|         </tr>{% endif %}<tr> | ||||
|             <th>Last Updated:</th> | ||||
|             <td>{{ pkg.last_update|date:"DATETIME_FORMAT" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td> | ||||
|             <td>{{ pkg.last_update|date:"Y-m-d H:i" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td> | ||||
|         </tr> | ||||
|         {% if user.is_authenticated %}<tr> | ||||
|             <th>Reproducible Status:</th> | ||||
| @@ -213,7 +213,7 @@ | ||||
| 	{% endif %} | ||||
|         {% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr> | ||||
|             <th>Last Flag Request:</th> | ||||
|             <td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date }}:<br/> | ||||
|             <td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date:"Y-m-d" }}:<br/> | ||||
|                 <div class="userdata">{{ flag_request.message|linebreaksbr|default:"{no message}" }}</div></td> | ||||
|         </tr>{% endif %}{% endwith %}{% endif %} | ||||
|     </table> | ||||
|   | ||||
| @@ -33,8 +33,8 @@ | ||||
|                 <td>{{ pkg.full_version }}</td> | ||||
|                 {% endif %} | ||||
|                 <td class="wrap">{{ pkg.pkgdesc }}</td> | ||||
|                 <td>{{ pkg.last_update|date }}</td> | ||||
|                 <td>{{ pkg.flag_date|date }}</td> | ||||
|                 <td>{{ pkg.last_update|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ pkg.flag_date|date:"Y-m-d" }}</td> | ||||
|             </tr> | ||||
|             {% endfor %} | ||||
|         </tbody> | ||||
|   | ||||
| @@ -62,7 +62,7 @@ | ||||
|             {% for pkg in exact_matches %} | ||||
|             <tr> | ||||
|                 <td>{{ pkg.arch.name }}</td> | ||||
|                 <td>{{ pkg.repo.name|capfirst }}</td> | ||||
|                 <td>{{ pkg.repo.name }}</td> | ||||
|                 <td>{% pkg_details_link pkg %}</td> | ||||
|                 {% if pkg.flag_date %} | ||||
|                 <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td> | ||||
| @@ -70,8 +70,8 @@ | ||||
|                 <td>{{ pkg.full_version }}</td> | ||||
|                 {% endif %} | ||||
|                 <td class="wrap">{{ pkg.pkgdesc }}</td> | ||||
|                 <td>{{ pkg.last_update|date }}</td> | ||||
|                 <td>{{ pkg.flag_date|date }}</td> | ||||
|                 <td>{{ pkg.last_update|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ pkg.flag_date|date:"Y-m-d" }}</td> | ||||
|             </tr> | ||||
|             {% endfor %} | ||||
|         </tbody> | ||||
| @@ -109,7 +109,7 @@ | ||||
|                     <td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td> | ||||
|                     {% endif %} | ||||
|                     <td>{{ pkg.arch.name }}</td> | ||||
|                     <td>{{ pkg.repo.name|capfirst }}</td> | ||||
|                     <td>{{ pkg.repo.name }}</td> | ||||
|                     <td>{% pkg_details_link pkg %}</td> | ||||
|                     {% if pkg.flag_date %} | ||||
|                     <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td> | ||||
| @@ -117,8 +117,8 @@ | ||||
|                     <td>{{ pkg.full_version }}</td> | ||||
|                     {% endif %} | ||||
|                     <td class="wrap">{{ pkg.pkgdesc }}</td> | ||||
|                     <td>{{ pkg.last_update|date }}</td> | ||||
|                     <td>{{ pkg.flag_date|date }}</td> | ||||
|                     <td>{{ pkg.last_update|date:"Y-m-d" }}</td> | ||||
|                     <td>{{ pkg.flag_date|date:"Y-m-d" }}</td> | ||||
|                 </tr> | ||||
|                 {% empty %} | ||||
|                 <tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr> | ||||
|   | ||||
| @@ -26,6 +26,10 @@ | ||||
|             <div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label> | ||||
|                 <input type="checkbox" name="repo_{{ repo_name|lower }}" id="id_repo_{{ repo_name|lower }}" class="repo_filter" value="{{ repo_name|lower }}" checked="checked"/></div> | ||||
|             {% endfor %} | ||||
|             {% if user.is_authenticated %} | ||||
|             <div><label for="id_mine_only" title="Show only packages packaged by me">Only Mine</label> | ||||
|                 <input type="checkbox" name="mine_only" id="id_mine_only" value="mine_only"/></div> | ||||
|             {% endif %} | ||||
|             <div><label for="id_pending" title="Packages with not enough signoffs">Only Pending Approval</label> | ||||
|                 <input type="checkbox" name="pending" id="id_pending" value="pending"/></div> | ||||
|             <div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div> | ||||
| @@ -50,13 +54,14 @@ | ||||
|             </tr> | ||||
|         </thead> | ||||
|         <tbody id="tbody_signoffs"> | ||||
|             {% for group in signoff_groups %}<tr class="{{ group.arch.name }} {{ group.target_repo|lower }}"> | ||||
|             {% for group in signoff_groups %} | ||||
|             <tr class="{% if user == group.packager %} mine{% endif %} {{ group.arch.name }} {{ group.target_repo|lower }}"> | ||||
|                 <td>{% pkg_details_link group.package %} {{ group.version }}</td> | ||||
|                 <td>{{ group.arch.name }}</td> | ||||
|                 <td>{{ group.target_repo }}</td> | ||||
|                 <td>{{ group.packager|default:"Unknown" }}</td> | ||||
|                 <td>{{ group.packages|length }}</td> | ||||
|                 <td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date }}</td> | ||||
|                 <td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date:"Y-m-d" }}</td> | ||||
|                 {% if group.specification.known_bad %} | ||||
|                 <td class="approval signoff-bad">Bad</td> | ||||
|                 {% else %} | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
|         <a href="{{ entry.url }}" | ||||
|             title="View full article: {{ entry.title }}">{{ entry.title }}</a> | ||||
|     </h4> | ||||
|     <p class="timestamp">{{ entry.publishdate|date }}</p> | ||||
|     <p class="timestamp">{{ entry.publishdate|date:"Y-m-d" }}</p> | ||||
|     <div class="article-content"> | ||||
|         {{ entry.summary |safe }} | ||||
|     </div> | ||||
|   | ||||
| @@ -49,6 +49,11 @@ | ||||
|                     <td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}" | ||||
|                             title="Visit the website for {{ dev.get_full_name }}"> | ||||
|                             {{ prof.website }}</a>{% endif %}</td> | ||||
|                 </tr><tr> | ||||
|                     <th>Social:</th> | ||||
|                     <td>{% if prof.social %}<a itemprop="url" href="{{ prof.social }}" | ||||
|                             title="Visit social account for {{ dev.get_full_name }}" rel="me"> | ||||
|                             {{ prof.social }}</a>{% endif %}</td> | ||||
|                 </tr><tr> | ||||
|                     <th>Occupation:</th> | ||||
|                     <td>{{ prof.occupation }}</td> | ||||
|   | ||||
| @@ -53,17 +53,6 @@ | ||||
|     <img src="{% static "nitrokey_logo.png" %}" | ||||
|     class="sponsor-btn-nitrokey" title="" alt="Nitrokey logo"/></a> | ||||
| 
 | ||||
|     <p>We would also like to thank <a href="https://www.privateinternetaccess.com/" | ||||
|     title="Private Internet Access">Private Internet Access</a> for sponsoring | ||||
|     dedicated servers across the globe. Private Internet Access is the leading | ||||
|     VPN Service provider specializing in secure, encrypted VPN tunnels which | ||||
|     create several layers of privacy and security providing users safety on the | ||||
|     internet.</p> | ||||
| 
 | ||||
|     <a href="https://www.privateinternetaccess.com/" title="Private Internet Access"> | ||||
|     <img src="{% static "pia_logo.png" %}" | ||||
|     class="sponsor-btn-pia" title="" alt="Private Internet Access logo"/></a> | ||||
| 
 | ||||
|     <p>We would also like to thank <a href="https://www.shells.com/" | ||||
|     title="Shells">Shells.com</a> for their monetary donation. | ||||
|     Shells provides you with a 1-click, powerful virtual desktop environment, | ||||
| @@ -74,16 +63,10 @@ | ||||
|     <img src="{% static "shells_logo.png" %}" | ||||
|     title="" alt="Shells logo"/></a> | ||||
| 
 | ||||
|     <p>We would also like to thank <a href="https://uptimerobot.com/" | ||||
|     title="UptimeRobot">UptimeRobot</a> for providing their monitoring service to us. | ||||
|     UptimeRobot is a leading uptime monitoring service.</p> | ||||
| 
 | ||||
|     <a href="https://uptimerobot.com/" title="UptimeRobot"> | ||||
|     <img src="{% static "uptimerobot_logo.png" %}" | ||||
|     title="" alt="UptimeRobot logo"/></a> | ||||
| 
 | ||||
|     <h3>Past donors</h3> | ||||
| 
 | ||||
|     <p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> & <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p> | ||||
| 
 | ||||
|     <div id="donor-list"> | ||||
|         <ul> | ||||
| {% for donor in donors %} | ||||
|   | ||||
| @@ -25,6 +25,8 @@ | ||||
|     It is intended for new installations only; an existing Arch Linux system | ||||
|     can always be updated with <code>pacman -Syu</code>.</p> | ||||
| 
 | ||||
|     <p>Images for installing Arch can be downloaded via <a href="#bittorrent-download">BitTorrent</a> or right here in your browser from one of the <a href="#http-downloads">Arch HTTP(S) mirrors down below</a>.</p> | ||||
| 
 | ||||
|     <ul> | ||||
|         {% if release.version %}<li><strong>Current Release:</strong> {{ release.version }}</li>{% endif %} | ||||
|         {% if release.kernel_version %}<li><strong>Included Kernel:</strong> {{ release.kernel_version }}</li>{% endif %} | ||||
| @@ -52,7 +54,7 @@ | ||||
|     to update your existing system. You may be looking for | ||||
|     <a href="{% url 'mirrorlist' %}">an updated mirrorlist</a> instead.</p> | ||||
| 
 | ||||
|     <h3>BitTorrent Download (recommended)</h3> | ||||
|     <h3 id="bittorrent-download">BitTorrent Download (recommended)</h3> | ||||
| 
 | ||||
|     <p>If you can spare the bytes, please leave the client open after your | ||||
|     download is finished, so you can seed it back to others. | ||||
| @@ -75,13 +77,6 @@ | ||||
|             title="Arch Linux Netboot">Arch Linux Netboot</a></li> | ||||
|     </ul> | ||||
| 
 | ||||
|     <h3>Vagrant images</h3> | ||||
| 
 | ||||
|     <p>Vagrant images for libvirt and virtualbox are available on the <a href="https://app.vagrantup.com/archlinux/boxes/archlinux">Vagrant Cloud</a>. You can bootstrap the image with the following commands:</p> | ||||
|     <code>vagrant init archlinux/archlinux</code> | ||||
|     <br/> | ||||
|     <code>vagrant up</code> | ||||
| 
 | ||||
|     <h3>Docker image</h3> | ||||
| 
 | ||||
|     <p>The official Docker image is available on <a href="https://hub.docker.com/_/archlinux/">Docker Hub</a>. You can run the image with the following command:</p> | ||||
| @@ -91,10 +86,18 @@ | ||||
| 
 | ||||
|     <p>Official virtual machine images are available for download on our <a href="https://gitlab.archlinux.org/archlinux/arch-boxes/-/packages">GitLab instance</a>, more information is available in the <a href="https://gitlab.archlinux.org/archlinux/arch-boxes/">README</a>.</p> | ||||
| 
 | ||||
|     <h3>HTTP Direct Downloads</h3> | ||||
|     <h3>WSL images</h3> | ||||
| 
 | ||||
|     <p>The official WSL image can be installed with the following command (in a PowerShell prompt from a Windows system with WSL 2 installed):</p> | ||||
|     <code>wsl --install archlinux</code> | ||||
| 
 | ||||
|     <p>It is also available for download on <a href="https://geo.mirror.pkgbuild.com/wsl/latest">mirrors</a>.</p> | ||||
|     <p>More information available in the <a href="https://wiki.archlinux.org/title/Install_Arch_Linux_on_WSL">Wiki</a>.</p> | ||||
| 
 | ||||
|     <h3 id="http-downloads">HTTP Direct Downloads</h3> | ||||
| 
 | ||||
|     <p>In addition to the BitTorrent links above, install images can also be | ||||
|     downloaded via HTTP from the mirror sites listed below. Please | ||||
|     downloaded via HTTP from the <a href="#download-mirrors">mirror sites listed below</a>. Please | ||||
|     ensure the download image matches the checksum from the <code>sha256sums.txt</code> or <code>b2sums.txt</code> file linked below.</p> | ||||
| 
 | ||||
|     <h4 id="checksums">Checksums and signatures</h4> | ||||
| @@ -130,15 +133,15 @@ | ||||
|     <pre><code>$ b2sum -c b2sums.txt</code></pre> | ||||
| 
 | ||||
|     To verify the PGP signature using Sequoia, first download the release signing key from WKD: | ||||
|     <pre><code>$ sq network wkd fetch {{ release.wkd_email }} -o release-key.pgp</code></pre> | ||||
|     <pre><code>$ sq network wkd search {{ release.wkd_email }} --output release-key.pgp</code></pre> | ||||
| 
 | ||||
|     With this signing key, verify the signature: | ||||
|     <pre><code>$ sq verify --signer-file release-key.pgp --detached archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre> | ||||
|     <pre><code>$ sq verify --signer-file release-key.pgp --signature-file archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre> | ||||
| 
 | ||||
|     Alternatively, using GnuPG, download the signing key from WKD: | ||||
|     <pre><code>$ gpg --auto-key-locate clear,wkd -v --locate-external-key {{ release.wkd_email }}</code></pre> | ||||
|     Verify the signature: | ||||
|     <pre><code>$ gpg --keyserver-options auto-key-retrieve --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre> | ||||
|     <pre><code>$ gpg --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre> | ||||
| 
 | ||||
|     {% cache 600 download-mirrors %} | ||||
|     <div id="download-mirrors"> | ||||
|   | ||||
| @@ -50,7 +50,7 @@ | ||||
|         <a href="{{ news.get_absolute_url }}" | ||||
|             title="View full article: {{ news.title }}">{{ news.title }}</a> | ||||
|     </h4> | ||||
|     <p class="timestamp">{{ news.postdate|date }}</p> | ||||
|     <p class="timestamp">{{ news.postdate|date:"Y-m-d" }}</p> | ||||
|     <div class="article-content"> | ||||
|         {% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }} | ||||
|         {% else %}{{ news.html|truncatewords_html:100 }}{% endif %} | ||||
| @@ -63,7 +63,7 @@ | ||||
|     </h3> | ||||
|     <dl class="newslist"> | ||||
|     {% endif %} | ||||
|         <dt>{{ news.postdate|date }}</dt> | ||||
|         <dt>{{ news.postdate|date:"Y-m-d" }}</dt> | ||||
|         <dd> | ||||
|             <a href="{{ news.get_absolute_url }}" | ||||
|                 title="View full article: {{ news.title }}">{{ news.title }}</a> | ||||
| @@ -133,8 +133,6 @@ | ||||
|     <h4>Support</h4> | ||||
|     <ul> | ||||
|         <li><a href="{% url 'page-donate' %}" title="Help support Arch Linux">Donate</a></li> | ||||
|         <li><a href="https://www.unixstickers.com/tag/archlinux" title="Arch | ||||
| 	Linux stickers, t-shirts, hoodies, mugs, posters and pins">Products via Unixstickers</a></li> | ||||
|         <li><a href="https://www.freewear.org/?page=list_items&org=Archlinux" | ||||
|             title="T-shirts">T-shirts via Freewear</a></li> | ||||
|         <li><a href="https://www.hellotux.com/arch" | ||||
| @@ -202,11 +200,6 @@ | ||||
|             title="" alt="Hetzner logo"/> | ||||
|     </a> | ||||
| 
 | ||||
|     <a href="https://www.privateinternetaccess.com/" title="Private Internet Access"> | ||||
|         <img src="{% static "pia_button.png" %}" | ||||
|             title="" alt="Private Internet Access logo"/> | ||||
|     </a> | ||||
| 
 | ||||
|     <a href="https://icons8.com/" title="Icons8"> | ||||
|         <img src="{% static "icons8_logo.png" %}" | ||||
|             title="" alt="Icons8 logo"/> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     <h2>{{ release.version }}</h2> | ||||
| 
 | ||||
|     <ul> | ||||
|         <li><strong>Release Date:</strong> {{ release.release_date|date }}</li> | ||||
|         <li><strong>Release Date:</strong> {{ release.release_date|date:"Y-m-d" }}</li> | ||||
|         {% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %} | ||||
|         <li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li> | ||||
|         {% if release.torrent_data %} | ||||
| @@ -38,7 +38,7 @@ | ||||
| 
 | ||||
|     <ul> | ||||
|         <li><strong>Comment:</strong> {{ torrent.comment }}</li> | ||||
|         <li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"DATETIME_FORMAT" }} UTC</li> | ||||
|         <li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"Y-m-d H:i" }} UTC</li> | ||||
|         <li><strong>Created By:</strong> {{ torrent.created_by }}</li> | ||||
|         <li><strong>Announce URL:</strong> {{ torrent.announce }}</li> | ||||
|         <li><strong>File Name:</strong> {{ torrent.file_name }}</li> | ||||
|   | ||||
| @@ -42,7 +42,7 @@ | ||||
|                     <a href="{{ item.magnet_uri }}" | ||||
|                        title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a> | ||||
|                 {% endif %}</td> | ||||
|                 <td>{{ item.release_date|date }}</td> | ||||
|                 <td>{{ item.release_date|date:"Y-m-d" }}</td> | ||||
|                 <td><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td> | ||||
|                 <td>{{ item.kernel_version|default:"" }}</td> | ||||
|                 <td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td> | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|             <tr> | ||||
|                 <td class="wrap"><a href="{{ list.get_absolute_url }}" | ||||
|                         title="View todo list: {{ list.name }}">{{ list.name }}</a></td> | ||||
|                 <td>{{ list.created|date }}</td> | ||||
|                 <td>{{ list.created|date:"Y-m-d" }}</td> | ||||
|                 <td>{{ list.creator.get_full_name }}</td> | ||||
|                 <td>{{ list.pkg_count }}</td> | ||||
|                 <td>{{ list.incomplete_count }}</td> | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|         {% endif %} | ||||
|     </ul> | ||||
| 
 | ||||
|     <div class="todo-info">{{ list.created|date }} - {{ list.creator.get_full_name }}</div> | ||||
|     <div class="todo-info">{{ list.created|date:"Y-m-d" }} - {{ list.creator.get_full_name }}</div> | ||||
| 
 | ||||
|     <div class="todo-description"> | ||||
|         {{list.stripped_description|default:'(no description)'|urlize|linebreaks}} | ||||
|   | ||||
| @@ -28,7 +28,9 @@ class Migration(migrations.Migration): | ||||
|                 ('created', models.DateTimeField(db_index=True)), | ||||
|                 ('last_modified', models.DateTimeField(editable=False)), | ||||
|                 ('raw', models.TextField(blank=True)), | ||||
|                 ('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_todolists', to=settings.AUTH_USER_MODEL)), | ||||
|                 ('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, | ||||
|                                               related_name='created_todolists', | ||||
|                                               to=settings.AUTH_USER_MODEL)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'get_latest_by': 'created', | ||||
| @@ -43,13 +45,16 @@ class Migration(migrations.Migration): | ||||
|                 ('created', models.DateTimeField(editable=False)), | ||||
|                 ('last_modified', models.DateTimeField(editable=False)), | ||||
|                 ('removed', models.DateTimeField(blank=True, null=True)), | ||||
|                 ('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')], default=0)), | ||||
|                 ('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')], | ||||
|                                                     default=0)), | ||||
|                 ('comments', models.TextField(blank=True, null=True)), | ||||
|                 ('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')), | ||||
|                 ('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Package')), | ||||
|                 ('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, | ||||
|                                           to='main.Package')), | ||||
|                 ('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Repo')), | ||||
|                 ('todolist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todolists.Todolist')), | ||||
|                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), | ||||
|                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, | ||||
|                                            to=settings.AUTH_USER_MODEL)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'get_latest_by': 'created', | ||||
|   | ||||
| @@ -13,6 +13,7 @@ class Migration(migrations.Migration): | ||||
|         migrations.AddField( | ||||
|             model_name='todolist', | ||||
|             name='kind', | ||||
|             field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0, help_text='(Rebuild for soname bumps, Task for independent tasks)'), | ||||
|             field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0, | ||||
|                                            help_text='(Rebuild for soname bumps, Task for independent tasks)'), | ||||
|         ), | ||||
|     ] | ||||
|   | ||||
| @@ -23,7 +23,8 @@ class Todolist(models.Model): | ||||
|     description = models.TextField() | ||||
|     creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists") | ||||
|     created = models.DateTimeField(db_index=True) | ||||
|     kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES, help_text='(Rebuild for soname bumps, Task for independent tasks)') | ||||
|     kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES, | ||||
|                                     help_text='(Rebuild for soname bumps, Task for independent tasks)') | ||||
|     last_modified = models.DateTimeField(editable=False) | ||||
|     raw = models.TextField(blank=True) | ||||
| 
 | ||||
|   | ||||