Compare commits
	
		
			65 Commits
		
	
	
		
			v24.8.9
			...
			b386d89d6c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						b386d89d6c
	
				 | 
					
					
						|||
| 9e36ad4e38 | |||
| 
						 | 
					2027adaaba | ||
| 
						 | 
					05abc7f386 | ||
| 
						 | 
					c1d70301c9 | ||
| 
						 | 
					323002ffe1 | ||
| 
						 | 
					a49a2cadc5 | ||
| 
						 | 
					a733efa173 | ||
| 
						 | 
					ce776a6297 | ||
| 
						 | 
					7126b7b006 | ||
| 
						 | 
					ba4eb27ae3 | ||
| 
						 | 
					375713717a | ||
| 93e59ab09b | |||
| 
						 | 
					5e33be47a7 | ||
| 
						 | 
					4473a9cdba | ||
| 49d616ef32 | |||
| 12f795bcf8 | |||
| 
						 | 
					24b21bffed | ||
| 
						 | 
					7f5d7e9082 | ||
| 
						 | 
					653b482ec5 | ||
| 
						 | 
					ecc4bdf0a7 | ||
| 
						 | 
					a45b88da4b | ||
| 
						 | 
					e460ba4727 | ||
| 
						 | 
					910e428baa | ||
| 8e6bc69713 | |||
| 
						 | 
					f624f5677b | ||
| 
						 | 
					2064099696 | ||
| 
						 | 
					67209075c5 | ||
| 
						 | 
					336d686ca2 | ||
| 
						 | 
					4f0e24f1f7 | ||
| 
						
						
							
						
						7bf65b63ff
	
				 | 
					
					
						|||
| 188ead820d | |||
| 
						 | 
					e07054c8ea | ||
| 
						 | 
					97aae09dce | ||
| 
						 | 
					0ce1a0ea5f | ||
| 
						 | 
					f38770be76 | ||
| 
						 | 
					2d39dc6379 | ||
| 
						 | 
					df4b0bfd67 | ||
| 
						 | 
					5da7fa80c5 | ||
| 
						 | 
					8d495d4fa7 | ||
| 
						 | 
					796c3f410f | ||
| 
						 | 
					37687bf9e4 | ||
| 7accacd5fd | |||
| 
						
						
							
						
						10fecc63e9
	
				 | 
					
					
						|||
| 368e248efc | |||
| 
						
						
							
						
						a71b1f783e
	
				 | 
					
					
						|||
| 
						
						
							
						
						77531b1948
	
				 | 
					
					
						|||
| 
						 | 
					2bf2fa235f | ||
| 
						 | 
					87a0d37953 | ||
| 
						 | 
					4210a46f9a | ||
| 
						 | 
					bd4d50b84e | ||
| 
						 | 
					fcd473608c | ||
| 74bfaed558 | |||
| 
						 | 
					4e25b014e4 | ||
| 
						
						
							
						
						1a1d963ca4
	
				 | 
					
					
						|||
| f8d5473c25 | |||
| 
						 | 
					c15b22203a | ||
| 
						 | 
					b39c4c9385 | ||
| 43c333727d | |||
| 
						 | 
					3da96a4007 | ||
| eda6dd9f3f | |||
| 
						 | 
					fc191bbe84 | ||
| 
						 | 
					c68204d6bb | ||
| 
						
						
							
						
						004064c15c
	
				 | 
					
					
						|||
| 
						
						
							
						
						305cbdc3d8
	
				 | 
					
					
						
							
								
								
									
										2
									
								
								.flake8
									
									
									
									
									
								
							
							
						
						@@ -1,3 +1,3 @@
 | 
				
			|||||||
[flake8]
 | 
					[flake8]
 | 
				
			||||||
max-line-length = 300
 | 
					max-line-length = 118
 | 
				
			||||||
ignore = E731, E241, E741
 | 
					ignore = E731, E241, E741
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/build-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -22,7 +22,7 @@ jobs:
 | 
				
			|||||||
      REGISTRY: gitea.artixlinux.org
 | 
					      REGISTRY: gitea.artixlinux.org
 | 
				
			||||||
      DH_REGISTRY: docker.io
 | 
					      DH_REGISTRY: docker.io
 | 
				
			||||||
      REPO_ORG: ${{ gitea.repository_owner }}
 | 
					      REPO_ORG: ${{ gitea.repository_owner }}
 | 
				
			||||||
      DH_ORG: corysanin
 | 
					      DH_ORG: artixlinux
 | 
				
			||||||
      IMAGE_NAME: archweb
 | 
					      IMAGE_NAME: archweb
 | 
				
			||||||
      DH_IMAGE_NAME: archweb
 | 
					      DH_IMAGE_NAME: archweb
 | 
				
			||||||
      ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }}
 | 
					      ABSOLUTE_IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_ORG }}/${{ env.IMAGE_NAME }}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -9,14 +9,17 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
    - uses: actions/checkout@v3
 | 
					    - uses: actions/checkout@v3
 | 
				
			||||||
    - name: Set up Python 3.11
 | 
					    - name: Set up Python 3.13
 | 
				
			||||||
      uses: actions/setup-python@v4
 | 
					      uses: actions/setup-python@v4
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: 3.11
 | 
					        python-version: 3.13
 | 
				
			||||||
    - name: Install dependencies
 | 
					    - name: Install dependencies
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        python -m pip install --upgrade pip
 | 
					        python -m pip install --upgrade pip
 | 
				
			||||||
        pip install -r requirements.txt && pip install -r requirements_test.txt
 | 
					        pip install -r requirements.txt && pip install -r requirements_test.txt && pip install ruff
 | 
				
			||||||
 | 
					    - name: Run ruff
 | 
				
			||||||
 | 
					      run: |
 | 
				
			||||||
 | 
					        ruff check .
 | 
				
			||||||
    - name: Lint with flake8
 | 
					    - name: Lint with flake8
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        make lint
 | 
					        make lint
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
FROM python:3.12-alpine3.20 AS base
 | 
					FROM python:3.13-alpine3.20 AS base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
 | 
					RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -131,7 +131,7 @@ Archweb provides multiple management commands for importing various sorts of dat
 | 
				
			|||||||
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
 | 
					* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
 | 
				
			||||||
* donor_import - Import a single donator from a mail passed to stdin
 | 
					* donor_import - Import a single donator from a mail passed to stdin
 | 
				
			||||||
* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
 | 
					* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
 | 
				
			||||||
* mirrorresolv - Poll every active mirror URLs and determine wheteher they have IP4 and/or IPv6 addresses.
 | 
					* mirrorresolv - Poll every active mirror URLs and determine whether they have IP4 and/or IPv6 addresses.
 | 
				
			||||||
* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
 | 
					* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
 | 
				
			||||||
* update_planet - Import all feeds for users who have a valid website and website_rss in their user profile.
 | 
					* update_planet - Import all feeds for users who have a valid website and website_rss in their user profile.
 | 
				
			||||||
* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.
 | 
					* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,8 @@ class Database(object):
 | 
				
			|||||||
                        retry = False
 | 
					                        retry = False
 | 
				
			||||||
                    except OperationalError as exc:
 | 
					                    except OperationalError as exc:
 | 
				
			||||||
                        retry_count += 1
 | 
					                        retry_count += 1
 | 
				
			||||||
                        logger.error('Unable to update database \'%s\', retrying=%d', self.path, retry_count, exc_info=exc)
 | 
					                        logger.error('Unable to update database \'%s\', retrying=%d',
 | 
				
			||||||
 | 
					                                     self.path, retry_count, exc_info=exc)
 | 
				
			||||||
                        time.sleep(5)
 | 
					                        time.sleep(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if retry_count == self.retry_limit:
 | 
					                if retry_count == self.retry_limit:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
            arches = Arch.objects.filter(agnostic=False)
 | 
					            arches = Arch.objects.filter(agnostic=False)
 | 
				
			||||||
            repos = Repo.objects.all()
 | 
					            repos = Repo.objects.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        arch_path_map = {arch: None for arch in arches}
 | 
					        arch_path_map = dict.fromkeys(arches)
 | 
				
			||||||
        all_paths = set()
 | 
					        all_paths = set()
 | 
				
			||||||
        total_paths = 0
 | 
					        total_paths = 0
 | 
				
			||||||
        for arch in arches:
 | 
					        for arch in arches:
 | 
				
			||||||
@@ -89,7 +89,9 @@ class Command(BaseCommand):
 | 
				
			|||||||
        for name in all_paths:
 | 
					        for name in all_paths:
 | 
				
			||||||
            manager.add_watch(name, mask)
 | 
					            manager.add_watch(name, mask)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        handler = EventHandler(arch_paths=arch_path_map, filename_suffix='.links.tar.gz', callback_func=wrapper_read_links)
 | 
					        handler = EventHandler(arch_paths=arch_path_map,
 | 
				
			||||||
 | 
					                               filename_suffix='.links.tar.gz',
 | 
				
			||||||
 | 
					                               callback_func=wrapper_read_links)
 | 
				
			||||||
        return pyinotify.Notifier(manager, handler)
 | 
					        return pyinotify.Notifier(manager, handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -539,7 +539,9 @@ def parse_info(pkgname, filename, iofile):
 | 
				
			|||||||
        elif blockname:
 | 
					        elif blockname:
 | 
				
			||||||
            store[blockname].append(line)
 | 
					            store[blockname].append(line)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname, filename, line))
 | 
					            raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname,
 | 
				
			||||||
 | 
					                                                                                                 filename,
 | 
				
			||||||
 | 
					                                                                                                 line))
 | 
				
			||||||
    return store
 | 
					    return store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,7 +72,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
            arches = Arch.objects.filter(agnostic=False)
 | 
					            arches = Arch.objects.filter(agnostic=False)
 | 
				
			||||||
            repos = Repo.objects.all()
 | 
					            repos = Repo.objects.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        arch_path_map = {arch: None for arch in arches}
 | 
					        arch_path_map = dict.fromkeys(arches)
 | 
				
			||||||
        all_paths = set()
 | 
					        all_paths = set()
 | 
				
			||||||
        total_paths = 0
 | 
					        total_paths = 0
 | 
				
			||||||
        for arch in arches:
 | 
					        for arch in arches:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								devel/migrations/0011_userprofile_social.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('devel', '0010_merge_20230312_1527'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='userprofile',
 | 
				
			||||||
 | 
					            name='social',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, max_length=200, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
import zoneinfo
 | 
					import zoneinfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.contrib.auth.models import Group, User
 | 
					from django.contrib.auth.models import Group, User
 | 
				
			||||||
from django.core.validators import MaxValueValidator, MinValueValidator
 | 
					from django.core.validators import MaxValueValidator, MinValueValidator
 | 
				
			||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
@@ -39,6 +40,9 @@ class UserProfile(models.Model):
 | 
				
			|||||||
    website = models.URLField(max_length=200, null=True, blank=True)
 | 
					    website = models.URLField(max_length=200, null=True, blank=True)
 | 
				
			||||||
    website_rss = models.URLField(max_length=200, null=True, blank=True,
 | 
					    website_rss = models.URLField(max_length=200, null=True, blank=True,
 | 
				
			||||||
                                  help_text='RSS Feed of your website for planet.archlinux.org')
 | 
					                                  help_text='RSS Feed of your website for planet.archlinux.org')
 | 
				
			||||||
 | 
					    social = models.URLField(max_length=200, null=True, blank=True,
 | 
				
			||||||
 | 
					                             verbose_name="Social account URL",
 | 
				
			||||||
 | 
					                             help_text="Mastodon or Fediverse account URL")
 | 
				
			||||||
    yob = models.IntegerField("Year of birth", null=True, blank=True,
 | 
					    yob = models.IntegerField("Year of birth", null=True, blank=True,
 | 
				
			||||||
                              validators=[MinValueValidator(1950), MaxValueValidator(2500)])
 | 
					                              validators=[MinValueValidator(1950), MaxValueValidator(2500)])
 | 
				
			||||||
    country = CountryField(blank=True)
 | 
					    country = CountryField(blank=True)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -202,7 +202,8 @@ def non_existing_dependencies(packages):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def non_reproducible_packages(packages):
 | 
					def non_reproducible_packages(packages):
 | 
				
			||||||
    statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname'))
 | 
					    statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD,
 | 
				
			||||||
 | 
					                                                                pkg__pkgname__in=packages.values('pkgname'))
 | 
				
			||||||
    return linkify_non_reproducible_packages(statuses)
 | 
					    return linkify_non_reproducible_packages(statuses)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -227,7 +228,7 @@ def orphan_dependencies(packages):
 | 
				
			|||||||
    JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
 | 
					    JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
 | 
				
			||||||
    JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
 | 
					    JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
 | 
				
			||||||
    ORDER BY child.pkgname;
 | 
					    ORDER BY child.pkgname;
 | 
				
			||||||
    """
 | 
					    """  # noqa: E501
 | 
				
			||||||
    cursor.execute(query)
 | 
					    cursor.execute(query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for row in cursor.fetchall():
 | 
					    for row in cursor.fetchall():
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -216,7 +216,9 @@ def tier0_mirror_auth(request):
 | 
				
			|||||||
    token = credentials[1]
 | 
					    token = credentials[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    groups = Group.objects.filter(name__in=SELECTED_GROUPS)
 | 
					    groups = Group.objects.filter(name__in=SELECTED_GROUPS)
 | 
				
			||||||
    user = User.objects.filter(username=username, is_active=True, groups__in=groups).select_related('userprofile').first()
 | 
					    user = User.objects.filter(username=username,
 | 
				
			||||||
 | 
					                               is_active=True,
 | 
				
			||||||
 | 
					                               groups__in=groups).select_related('userprofile').first()
 | 
				
			||||||
    if not user:
 | 
					    if not user:
 | 
				
			||||||
        return unauthorized
 | 
					        return unauthorized
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,23 @@
 | 
				
			|||||||
version: '2'
 | 
					version: '2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Run the following once:
 | 
					# Run the following once:
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py migrate
 | 
					# docker compose run --rm archweb_web python manage.py migrate
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py loaddata main/fixtures/arches.json
 | 
					# docker compose run --rm archweb_web python manage.py loaddata mirrors/fixtures/mirrorprotocols.json
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py loaddata main/fixtures/repos.json
 | 
					# docker compose run --rm archweb_web python manage.py loaddata main/fixtures/arches.json
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local
 | 
					# docker compose run --rm archweb_web python manage.py loaddata main/fixtures/repos.json
 | 
				
			||||||
 | 
					# docker compose run --rm archweb_web python manage.py createsuperuser --username=admin --email=admin@artixweb.local
 | 
				
			||||||
## go to /admin and create a user according to overlay/devel/fixtures/user_profiles.json
 | 
					## go to /admin and create a user according to overlay/devel/fixtures/user_profiles.json
 | 
				
			||||||
## go to /admin/auth/user/2/change/ and add a name
 | 
					## go to /admin/auth/user/2/change/ and add a name
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py generate_keyring pgp.surfnet.nl ./config/keyring
 | 
					# docker compose run --rm archweb_web python manage.py generate_keyring pgp.surfnet.nl ./config/keyring
 | 
				
			||||||
# docker compose run --rm packages_web python manage.py pgp_import ./config/keyring
 | 
					# docker compose run --rm archweb_web python manage.py pgp_import ./config/keyring
 | 
				
			||||||
## go to /admin/devel/developerkey/ and set the owner (and parent) for the ownerless key
 | 
					## go to /admin/devel/developerkey/ and set the owner (and parent) for the ownerless key
 | 
				
			||||||
## go to /admin/sites/site/1/change/ and set the domain
 | 
					## go to /admin/sites/site/1/change/ and set the domain
 | 
				
			||||||
 | 
					## clone the mirrors repo
 | 
				
			||||||
 | 
					# docker compose run --rm archweb_web python manage.py loaddata /mirrors/mirrors.fixture.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
    packages_web:
 | 
					    archweb_web:
 | 
				
			||||||
        container_name: artixweb-packages
 | 
					        container_name: artixweb-packages
 | 
				
			||||||
        build:
 | 
					        build:
 | 
				
			||||||
            context: ./
 | 
					            context: ./
 | 
				
			||||||
@@ -25,7 +28,7 @@ services:
 | 
				
			|||||||
        volumes:
 | 
					        volumes:
 | 
				
			||||||
            - ./config:/usr/src/web/config
 | 
					            - ./config:/usr/src/web/config
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    packages_sync:
 | 
					    archweb_sync:
 | 
				
			||||||
        container_name: artixweb-sync
 | 
					        container_name: artixweb-sync
 | 
				
			||||||
        build:
 | 
					        build:
 | 
				
			||||||
            context: ./
 | 
					            context: ./
 | 
				
			||||||
@@ -35,11 +38,11 @@ services:
 | 
				
			|||||||
            - ./config:/usr/src/web/config
 | 
					            - ./config:/usr/src/web/config
 | 
				
			||||||
        command: ./downloadpackages.sh
 | 
					        command: ./downloadpackages.sh
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    packages_nginx:
 | 
					    archweb_nginx:
 | 
				
			||||||
        container_name: artixweb-nginx
 | 
					        container_name: artixweb-nginx
 | 
				
			||||||
        image: linuxserver/nginx:latest
 | 
					        image: linuxserver/nginx:latest
 | 
				
			||||||
        restart: "no"
 | 
					        restart: "no"
 | 
				
			||||||
        ports:
 | 
					        ports:
 | 
				
			||||||
            - "8080:80"
 | 
					            - "8080:80"
 | 
				
			||||||
        volumes:
 | 
					        volumes:
 | 
				
			||||||
            - ./nginx.conf:/config/nginx/site-confs/default.conf:ro
 | 
					            - ./nginx.conf:/config/nginx/site-confs/default.conf
 | 
				
			||||||
@@ -8,7 +8,7 @@ fi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
printf "downloadpackages.sh\nusing %s/\$repo/os/\$arch for mirror.\n" "$mirror"
 | 
					printf "downloadpackages.sh\nusing %s/\$repo/os/\$arch for mirror.\n" "$mirror"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins"
 | 
					repos="system world galaxy lib32 system-gremlins world-gremlins galaxy-gremlins lib32-gremlins system-goblins world-goblins galaxy-goblins lib32-goblins"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir -p ./archives
 | 
					mkdir -p ./archives
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,18 @@
 | 
				
			|||||||
            "testing": true
 | 
					            "testing": true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, 
 | 
					    }, 
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        "pk": 15, 
 | 
				
			||||||
 | 
					        "model": "main.repo", 
 | 
				
			||||||
 | 
					        "fields": {
 | 
				
			||||||
 | 
					            "bugs_category": 33, 
 | 
				
			||||||
 | 
					            "staging": true, 
 | 
				
			||||||
 | 
					            "name": "Extra-Staging",
 | 
				
			||||||
 | 
					            "bugs_project": 5, 
 | 
				
			||||||
 | 
					            "svn_root": "packages",
 | 
				
			||||||
 | 
					            "testing": false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, 
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "pk": 1, 
 | 
					        "pk": 1, 
 | 
				
			||||||
        "model": "main.repo", 
 | 
					        "model": "main.repo", 
 | 
				
			||||||
@@ -118,5 +130,17 @@
 | 
				
			|||||||
            "svn_root": "packages", 
 | 
					            "svn_root": "packages", 
 | 
				
			||||||
            "testing": true
 | 
					            "testing": true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        "pk": 16, 
 | 
				
			||||||
 | 
					        "model": "main.repo", 
 | 
				
			||||||
 | 
					        "fields": {
 | 
				
			||||||
 | 
					            "bugs_category": 10, 
 | 
				
			||||||
 | 
					            "staging": true, 
 | 
				
			||||||
 | 
					            "name": "Core-Staging",
 | 
				
			||||||
 | 
					            "bugs_project": 1, 
 | 
				
			||||||
 | 
					            "svn_root": "packages", 
 | 
				
			||||||
 | 
					            "testing": false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -280,7 +280,8 @@ class Package(models.Model):
 | 
				
			|||||||
            dep_pkgs = list(dep_pkgs)
 | 
					            dep_pkgs = list(dep_pkgs)
 | 
				
			||||||
            dep = dep_pkgs[0]
 | 
					            dep = dep_pkgs[0]
 | 
				
			||||||
            if len(dep_pkgs) > 1:
 | 
					            if len(dep_pkgs) > 1:
 | 
				
			||||||
                dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
 | 
					                dep_pkgs = [d for d in dep_pkgs
 | 
				
			||||||
 | 
					                            if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
 | 
				
			||||||
                if len(dep_pkgs) > 0:
 | 
					                if len(dep_pkgs) > 0:
 | 
				
			||||||
                    dep = dep_pkgs[0]
 | 
					                    dep = dep_pkgs[0]
 | 
				
			||||||
            trimmed.append(dep)
 | 
					            trimmed.append(dep)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
import cssmin
 | 
					 | 
				
			||||||
import jsmin
 | 
					import jsmin
 | 
				
			||||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
 | 
					from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
 | 
				
			||||||
from django.core.files.base import ContentFile
 | 
					from django.core.files.base import ContentFile
 | 
				
			||||||
from django.utils.encoding import smart_str
 | 
					from django.utils.encoding import smart_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cssmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
 | 
					class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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]))
 | 
				
			||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,5 +25,14 @@
 | 
				
			|||||||
            "default": false, 
 | 
					            "default": false, 
 | 
				
			||||||
            "protocol": "https"
 | 
					            "protocol": "https"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        "pk": 9, 
 | 
				
			||||||
 | 
					        "model": "mirrors.mirrorprotocol", 
 | 
				
			||||||
 | 
					        "fields": {
 | 
				
			||||||
 | 
					            "is_download": false, 
 | 
				
			||||||
 | 
					            "default": false, 
 | 
				
			||||||
 | 
					            "protocol": "ftp"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -184,12 +184,26 @@ def check_rsync_url(mirror_url, location, timeout):
 | 
				
			|||||||
        with open(os.devnull, 'w') as devnull:
 | 
					        with open(os.devnull, 'w') as devnull:
 | 
				
			||||||
            if logger.isEnabledFor(logging.DEBUG):
 | 
					            if logger.isEnabledFor(logging.DEBUG):
 | 
				
			||||||
                logger.debug("rsync cmd: %s", ' '.join(rsync_cmd))
 | 
					                logger.debug("rsync cmd: %s", ' '.join(rsync_cmd))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            start = time.time()
 | 
					            start = time.time()
 | 
				
			||||||
 | 
					            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)
 | 
					                proc = subprocess.Popen(rsync_cmd, stdout=devnull, stderr=subprocess.PIPE)
 | 
				
			||||||
            _, errdata = proc.communicate()
 | 
					                _, errdata = proc.communicate(timeout=rsync_subprocess_timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                end = time.time()
 | 
					                end = time.time()
 | 
				
			||||||
                log.duration = end - start
 | 
					                log.duration = end - start
 | 
				
			||||||
        if proc.returncode != 0:
 | 
					            except subprocess.TimeoutExpired:
 | 
				
			||||||
 | 
					                timeout_expired = True
 | 
				
			||||||
 | 
					                proc.kill()
 | 
				
			||||||
 | 
					                logger.debug("rsync command timeout error: %s, %s", url, errdata)
 | 
				
			||||||
 | 
					                log.is_success = False
 | 
				
			||||||
 | 
					                log.duration = None
 | 
				
			||||||
 | 
					                log.error = f"rsync subprocess killed after {rsync_subprocess_timeout} seconds"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if proc.returncode != 0 and not timeout_expired:
 | 
				
			||||||
            logger.debug("error: %s, %s", url, errdata)
 | 
					            logger.debug("error: %s, %s", url, errdata)
 | 
				
			||||||
            log.is_success = False
 | 
					            log.is_success = False
 | 
				
			||||||
            log.error = errdata.strip().decode('utf-8')
 | 
					            log.error = errdata.strip().decode('utf-8')
 | 
				
			||||||
@@ -197,7 +211,7 @@ def check_rsync_url(mirror_url, location, timeout):
 | 
				
			|||||||
            # don't record a duration as it is misleading
 | 
					            # don't record a duration as it is misleading
 | 
				
			||||||
            if proc.returncode in (1, 30, 35):
 | 
					            if proc.returncode in (1, 30, 35):
 | 
				
			||||||
                log.duration = None
 | 
					                log.duration = None
 | 
				
			||||||
        else:
 | 
					        elif not timeout_expired:
 | 
				
			||||||
            logger.debug("success: %s, %.2f", url, log.duration)
 | 
					            logger.debug("success: %s, %.2f", url, log.duration)
 | 
				
			||||||
            if os.path.exists(lastsync_path):
 | 
					            if os.path.exists(lastsync_path):
 | 
				
			||||||
                with open(lastsync_path, 'r') as lastsync:
 | 
					                with open(lastsync_path, 'r') as lastsync:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ def test_mirrorurl_get_full_url(mirrorurl):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def test_mirror_url_clean(mirrorurl):
 | 
					def test_mirror_url_clean(mirrorurl):
 | 
				
			||||||
    mirrorurl.clean()
 | 
					    mirrorurl.clean()
 | 
				
			||||||
    # TOOD(jelle): this expects HOSTNAME to resolve, maybe mock
 | 
					    # TODO(jelle): this expects HOSTNAME to resolve, maybe mock
 | 
				
			||||||
    assert mirrorurl.has_ipv4
 | 
					    assert mirrorurl.has_ipv4
 | 
				
			||||||
    # requires ipv6 on host... mock?
 | 
					    # requires ipv6 on host... mock?
 | 
				
			||||||
    # assert mirrorurl.has_ipv6 == True
 | 
					    # assert mirrorurl.has_ipv6 == True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,10 +54,12 @@ class NewsCreateView(CreateView):
 | 
				
			|||||||
            if settings.MAILMAN_PASSWORD:
 | 
					            if settings.MAILMAN_PASSWORD:
 | 
				
			||||||
                headers['Approved'] = settings.MAILMAN_PASSWORD
 | 
					                headers['Approved'] = settings.MAILMAN_PASSWORD
 | 
				
			||||||
            template = loader.get_template('news/news_email_notification.txt')
 | 
					            template = loader.get_template('news/news_email_notification.txt')
 | 
				
			||||||
 | 
					            author = newsitem.author.get_full_name()
 | 
				
			||||||
 | 
					            from_ = f'"Arch Linux: Recent news updates: {author}" <{settings.ANNOUNCE_EMAIL}>'
 | 
				
			||||||
            EmailMessage(
 | 
					            EmailMessage(
 | 
				
			||||||
                subject=f'[arch-announce] {newsitem.title}',
 | 
					                subject=f'[arch-announce] {newsitem.title}',
 | 
				
			||||||
                body=template.render(ctx),
 | 
					                body=template.render(ctx),
 | 
				
			||||||
                from_email=f'"Arch Linux: Recent news updates: {newsitem.author.get_full_name()}" <{settings.ANNOUNCE_EMAIL}>',
 | 
					                from_email=from_,
 | 
				
			||||||
                to=[settings.ANNOUNCE_EMAIL],
 | 
					                to=[settings.ANNOUNCE_EMAIL],
 | 
				
			||||||
                headers=headers).send()
 | 
					                headers=headers).send()
 | 
				
			||||||
        return super(NewsCreateView, self).form_valid(form)
 | 
					        return super(NewsCreateView, self).form_valid(form)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ server {
 | 
				
			|||||||
    try_files "" @proxy;
 | 
					    try_files "" @proxy;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  location ~ ^/(packages|groups|opensearch|feeds) {
 | 
					  location ~ ^/(packages|groups|opensearch|feeds|mirrors|mirrorlist) {
 | 
				
			||||||
    try_files "" @proxy;
 | 
					    try_files "" @proxy;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,20 +1,20 @@
 | 
				
			|||||||
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
 | 
					-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
 | 
				
			||||||
Django==5.0.8
 | 
					Django==5.1.9
 | 
				
			||||||
IPy==1.1
 | 
					IPy==1.1
 | 
				
			||||||
Markdown==3.3.7
 | 
					Markdown==3.3.7
 | 
				
			||||||
bencode.py==4.0.0
 | 
					bencode.py==4.0.0
 | 
				
			||||||
django-countries==7.6.1
 | 
					django-countries==7.6.1
 | 
				
			||||||
django-extensions==3.2.3
 | 
					django-extensions==4.1
 | 
				
			||||||
jsmin==3.0.1
 | 
					jsmin==3.0.1
 | 
				
			||||||
pgpdump==1.5
 | 
					pgpdump==1.5
 | 
				
			||||||
parse==1.19.0
 | 
					parse==1.20.2
 | 
				
			||||||
sqlparse==0.5.0
 | 
					sqlparse==0.5.0
 | 
				
			||||||
django-csp==3.7
 | 
					django-csp==4.0
 | 
				
			||||||
ptpython==2.0.4
 | 
					ptpython==2.0.4
 | 
				
			||||||
feedparser==6.0.10
 | 
					feedparser==6.0.11
 | 
				
			||||||
bleach==6.0.0
 | 
					bleach==6.0.0
 | 
				
			||||||
requests==2.32.0
 | 
					requests==2.32.4
 | 
				
			||||||
xtarfile==0.1.0
 | 
					xtarfile==0.2.1
 | 
				
			||||||
zstandard==0.17.0
 | 
					zstandard==0.23.0
 | 
				
			||||||
whitenoise==6.7.0
 | 
					whitenoise==6.8.2
 | 
				
			||||||
django-prometheus==2.3.1
 | 
					django-prometheus==2.3.1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,6 @@ select = [
 | 
				
			|||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ignore = [
 | 
					ignore = [
 | 
				
			||||||
    "E501", # line lengt violation
 | 
					 | 
				
			||||||
    "E731", # Do not assign a `lambda` expression, use a `def`
 | 
					    "E731", # Do not assign a `lambda` expression, use a `def`
 | 
				
			||||||
    "B904", # Within an `except` clause, raise exceptions with `raise ... from err`
 | 
					    "B904", # Within an `except` clause, raise exceptions with `raise ... from err`
 | 
				
			||||||
    "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
 | 
					    "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
 | 
				
			||||||
@@ -34,3 +33,8 @@ ignore = [
 | 
				
			|||||||
    "DJ008", # Model does not define `__str__` method
 | 
					    "DJ008", # Model does not define `__str__` method
 | 
				
			||||||
    "DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
 | 
					    "DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exclude = [
 | 
				
			||||||
 | 
					    "*/migrations/*.py",  # Ignore Django migrations
 | 
				
			||||||
 | 
					    "src/cssmin/*"  # cssmin, not our code
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,9 +41,6 @@ SITE_ID = 1
 | 
				
			|||||||
DATE_FORMAT = 'Y-m-d'
 | 
					DATE_FORMAT = 'Y-m-d'
 | 
				
			||||||
DATETIME_FORMAT = 'Y-m-d H:i'
 | 
					DATETIME_FORMAT = 'Y-m-d H:i'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Disable so our own DATE_FORMAT/DATETIME_FORMAT is used.
 | 
					 | 
				
			||||||
USE_L10N = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Login URL configuration
 | 
					# Login URL configuration
 | 
				
			||||||
LOGIN_URL = '/login/'
 | 
					LOGIN_URL = '/login/'
 | 
				
			||||||
LOGIN_REDIRECT_URL = '/'
 | 
					LOGIN_REDIRECT_URL = '/'
 | 
				
			||||||
@@ -270,7 +267,9 @@ if DEBUG_TOOLBAR:
 | 
				
			|||||||
    INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
 | 
					    INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if PROMETHEUS_METRICS:
 | 
					if PROMETHEUS_METRICS:
 | 
				
			||||||
    MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', *list(MIDDLEWARE), 'django_prometheus.middleware.PrometheusAfterMiddleware']
 | 
					    MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware',
 | 
				
			||||||
 | 
					                  *list(MIDDLEWARE),
 | 
				
			||||||
 | 
					                  'django_prometheus.middleware.PrometheusAfterMiddleware']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
 | 
					    INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -754,6 +754,10 @@ table.results {
 | 
				
			|||||||
        text-align: center;
 | 
					        text-align: center;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .results [hidden] {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* pkglist: layout */
 | 
					/* pkglist: layout */
 | 
				
			||||||
#pkglist-about {
 | 
					#pkglist-about {
 | 
				
			||||||
    margin-top: 1.5em;
 | 
					    margin-top: 1.5em;
 | 
				
			||||||
@@ -1056,12 +1060,12 @@ table td.country {
 | 
				
			|||||||
    background: #ffd;
 | 
					    background: #ffd;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.results tr:nth-child(even),
 | 
					.results tr:nth-child(even of :not([hidden])),
 | 
				
			||||||
#article-list tr:nth-child(even) {
 | 
					#article-list tr:nth-child(even) {
 | 
				
			||||||
    background: #e4eeff;
 | 
					    background: #e4eeff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.results tr:nth-child(odd),
 | 
					.results tr:nth-child(odd of :not([hidden])),
 | 
				
			||||||
#article-list tr:nth-child(odd) {
 | 
					#article-list tr:nth-child(odd) {
 | 
				
			||||||
    background: #fff;
 | 
					    background: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -212,8 +212,14 @@ function filter_pkgs_list(filter_ele, tbody_ele) {
 | 
				
			|||||||
        rows = rows.has('.incomplete');
 | 
					        rows = rows.has('.incomplete');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /* hide all rows, then show the set we care about */
 | 
					    /* hide all rows, then show the set we care about */
 | 
				
			||||||
    all_rows.hide();
 | 
					    // note that we don't use .hide() from jQuery because it adds display:none
 | 
				
			||||||
    rows.show();
 | 
					    // which is very expensive to query in CSS ([style*="display: none"])
 | 
				
			||||||
 | 
					    all_rows.each(function() {
 | 
				
			||||||
 | 
					        $(this).attr('hidden', true);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    rows.each(function() {
 | 
				
			||||||
 | 
					        $(this).removeAttr('hidden');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    $('#filter-count').text(rows.length);
 | 
					    $('#filter-count').text(rows.length);
 | 
				
			||||||
    /* make sure we update the odd/even styling from sorting */
 | 
					    /* make sure we update the odd/even styling from sorting */
 | 
				
			||||||
    $('.results').trigger('applyWidgets', [false]);
 | 
					    $('.results').trigger('applyWidgets', [false]);
 | 
				
			||||||
@@ -318,6 +324,10 @@ function filter_signoffs() {
 | 
				
			|||||||
    /* start with all rows, and then remove ones we shouldn't show */
 | 
					    /* start with all rows, and then remove ones we shouldn't show */
 | 
				
			||||||
    var rows = $('#tbody_signoffs').children(),
 | 
					    var rows = $('#tbody_signoffs').children(),
 | 
				
			||||||
        all_rows = rows;
 | 
					        all_rows = rows;
 | 
				
			||||||
 | 
					    /* apply the filters, cheaper ones first */
 | 
				
			||||||
 | 
					    if ($('#id_mine_only').is(':checked')) {
 | 
				
			||||||
 | 
					        rows = rows.filter('.mine');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    /* apply arch and repo filters */
 | 
					    /* apply arch and repo filters */
 | 
				
			||||||
    $('#signoffs_filter .arch_filter').add(
 | 
					    $('#signoffs_filter .arch_filter').add(
 | 
				
			||||||
            '#signoffs_filter .repo_filter').each(function() {
 | 
					            '#signoffs_filter .repo_filter').each(function() {
 | 
				
			||||||
@@ -330,8 +340,14 @@ function filter_signoffs() {
 | 
				
			|||||||
        rows = rows.has('td.signoff-no');
 | 
					        rows = rows.has('td.signoff-no');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /* hide all rows, then show the set we care about */
 | 
					    /* hide all rows, then show the set we care about */
 | 
				
			||||||
    all_rows.hide();
 | 
					    // note that we don't use .hide() from jQuery because it adds display:none
 | 
				
			||||||
    rows.show();
 | 
					    // which is very expensive to query in CSS ([style*="display: none"])
 | 
				
			||||||
 | 
					    all_rows.each(function() {
 | 
				
			||||||
 | 
					        $(this).attr('hidden', true);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    rows.each(function() {
 | 
				
			||||||
 | 
					        $(this).removeAttr('hidden');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    $('#filter-count').text(rows.length);
 | 
					    $('#filter-count').text(rows.length);
 | 
				
			||||||
    /* make sure we update the odd/even styling from sorting */
 | 
					    /* make sure we update the odd/even styling from sorting */
 | 
				
			||||||
    $('.results').trigger('applyWidgets', [false]);
 | 
					    $('.results').trigger('applyWidgets', [false]);
 | 
				
			||||||
@@ -340,6 +356,7 @@ function filter_signoffs() {
 | 
				
			|||||||
function filter_signoffs_reset() {
 | 
					function filter_signoffs_reset() {
 | 
				
			||||||
    $('#signoffs_filter .arch_filter').prop('checked', true);
 | 
					    $('#signoffs_filter .arch_filter').prop('checked', true);
 | 
				
			||||||
    $('#signoffs_filter .repo_filter').prop('checked', true);
 | 
					    $('#signoffs_filter .repo_filter').prop('checked', true);
 | 
				
			||||||
 | 
					    $('#id_mine_only').prop('checked', false);
 | 
				
			||||||
    $('#id_pending').prop('checked', false);
 | 
					    $('#id_pending').prop('checked', false);
 | 
				
			||||||
    filter_signoffs();
 | 
					    filter_signoffs();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,8 +104,7 @@ tr :nth-child(7) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media (prefers-color-scheme: dark),
 | 
					@media not all and (prefers-color-scheme: light) {
 | 
				
			||||||
(prefers-color-scheme: no-preference) {
 | 
					 | 
				
			||||||
    html body {
 | 
					    html body {
 | 
				
			||||||
        background: #1a1a1a;
 | 
					        background: #1a1a1a;
 | 
				
			||||||
        color: #d9d9d9;
 | 
					        color: #d9d9d9;
 | 
				
			||||||
@@ -146,6 +145,10 @@ tr :nth-child(7) {
 | 
				
			|||||||
        background-image: url(data:image/gif;base64,R0lGODlhFQAEAPABAOTu/wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAgABACwAAAAAFQAEAAACDYwfoAvoz9qbZ9FrJC0AOw==);
 | 
					        background-image: url(data:image/gif;base64,R0lGODlhFQAEAPABAOTu/wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAgABACwAAAAAFQAEAAACDYwfoAvoz9qbZ9FrJC0AOw==);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    code {
 | 
				
			||||||
 | 
					        background: #334450;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .results.results td,
 | 
					    .results.results td,
 | 
				
			||||||
    .results.results th {
 | 
					    .results.results th {
 | 
				
			||||||
        border: 1px solid #858585;
 | 
					        border: 1px solid #858585;
 | 
				
			||||||
@@ -159,7 +162,7 @@ tr :nth-child(7) {
 | 
				
			|||||||
        background-color: #111;
 | 
					        background-color: #111;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .results th {
 | 
					    .results th, #pkgsearch {
 | 
				
			||||||
        color: #fff;
 | 
					        color: #fff;
 | 
				
			||||||
        background-color: #0f3147;
 | 
					        background-color: #0f3147;
 | 
				
			||||||
        border: 1px solid #0A6682;
 | 
					        border: 1px solid #0A6682;
 | 
				
			||||||
 
 | 
				
			|||||||
| 
		 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>
 | 
					    <tbody>
 | 
				
			||||||
        {% for entry in admin_log %}
 | 
					        {% for entry in admin_log %}
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
            <th scope="row">{{ entry.action_time|date:"DATETIME_FORMAT" }}</th>
 | 
					            <th scope="row">{{ entry.action_time|date:"Y-m-d H:i" }}</th>
 | 
				
			||||||
            {% if log_user %}
 | 
					            {% if log_user %}
 | 
				
			||||||
            <td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td>
 | 
					            <td>{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}</td>
 | 
				
			||||||
            {% else %}
 | 
					            {% else %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,8 +36,8 @@
 | 
				
			|||||||
                        {% endif %}{% endwith %}</td>
 | 
					                        {% endif %}{% endwith %}</td>
 | 
				
			||||||
                    <td>{{ pkg.repo.name }}</td>
 | 
					                    <td>{{ pkg.repo.name }}</td>
 | 
				
			||||||
                    <td>{{ pkg.arch.name }}</td>
 | 
					                    <td>{{ pkg.arch.name }}</td>
 | 
				
			||||||
                    <td>{{ pkg.flag_date|date }}</td>
 | 
					                    <td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
                    <td>{{ pkg.last_update|date }}</td>
 | 
					                    <td>{{ pkg.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
            {% empty %}
 | 
					            {% empty %}
 | 
				
			||||||
                <tr class="empty"><td colspan="7"><em>No flagged packages to display</em></td></tr>
 | 
					                <tr class="empty"><td colspan="7"><em>No flagged packages to display</em></td></tr>
 | 
				
			||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
                <td>{{ group.version }}</td>
 | 
					                <td>{{ group.version }}</td>
 | 
				
			||||||
                <td>{{ group.arch.name }}</td>
 | 
					                <td>{{ group.arch.name }}</td>
 | 
				
			||||||
                <td>{{ group.target_repo }}</td>
 | 
					                <td>{{ group.target_repo }}</td>
 | 
				
			||||||
                <td>{{ group.last_update|date }}</td>
 | 
					                <td>{{ group.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                {% if group.specification.known_bad %}
 | 
					                {% if group.specification.known_bad %}
 | 
				
			||||||
                <td class="approval signoff-bad">Bad</td>
 | 
					                <td class="approval signoff-bad">Bad</td>
 | 
				
			||||||
                {% else %}
 | 
					                {% else %}
 | 
				
			||||||
@@ -138,7 +138,7 @@
 | 
				
			|||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td class="wrap"><a href="{{ todo.get_absolute_url }}"
 | 
					                <td class="wrap"><a href="{{ todo.get_absolute_url }}"
 | 
				
			||||||
                        title="View todo list: {{ todo.name }}">{{ todo.name }}</a></td>
 | 
					                        title="View todo list: {{ todo.name }}">{{ todo.name }}</a></td>
 | 
				
			||||||
                <td>{{ todo.created|date }}</td>
 | 
					                <td>{{ todo.created|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ todo.creator.get_full_name }}</td>
 | 
					                <td>{{ todo.creator.get_full_name }}</td>
 | 
				
			||||||
                <td>{{ todo.pkg_count }}</td>
 | 
					                <td>{{ todo.pkg_count }}</td>
 | 
				
			||||||
                <td>{{ todo.incomplete_count }}</td>
 | 
					                <td>{{ todo.incomplete_count }}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,9 +60,9 @@
 | 
				
			|||||||
                {% else %}
 | 
					                {% else %}
 | 
				
			||||||
                <td>{{ pkg.full_version }}</td>
 | 
					                <td>{{ pkg.full_version }}</td>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                <td>{{ pkg.last_update|date }}</td>
 | 
					                <td>{{ pkg.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ pkg.build_date|date }}</td>
 | 
					                <td>{{ pkg.build_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ pkg.flag_date|date }}</td>
 | 
					                <td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
                {% for attr in column_attrs %}
 | 
					                {% for attr in column_attrs %}
 | 
				
			||||||
                <td>{{ pkg|attribute:attr }}</td>
 | 
					                <td>{{ pkg|attribute:attr }}</td>
 | 
				
			||||||
                {% endfor %}
 | 
					                {% endfor %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
<div class="box">
 | 
					<div class="box">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <h2>Tier 0 Mirror usage information</h2>
 | 
					    <h2>Tier 0 Mirror usage information</h2>
 | 
				
			||||||
    <p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with a HTTP Basic Auth password unique per Staff member.</p>
 | 
					    <p>Arch Linux Tier 0 mirror on <a href="https://repos.archlinux.org">repos.archlinux.org</a> which can be used if to obtain the absolute latest packages. The mirror is protected with an HTTP Basic Auth password unique per Staff member.</p>
 | 
				
			||||||
    {% if mirror_url %}
 | 
					    {% if mirror_url %}
 | 
				
			||||||
      <code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button>
 | 
					      <code id="serverinfo">Server = {{ mirror_url }}</code> <button id="copybutton">Copy to clipboard</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@
 | 
				
			|||||||
        <tbody>
 | 
					        <tbody>
 | 
				
			||||||
            {% for item in news_list %}
 | 
					            {% for item in news_list %}
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td>{{ item.postdate|date }}</td>
 | 
					                <td>{{ item.postdate|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td class="wrap"><a href="{{ item.get_absolute_url }}"
 | 
					                <td class="wrap"><a href="{{ item.get_absolute_url }}"
 | 
				
			||||||
                        title="View: {{ item.title }}">{{ item.title }}</a></td>
 | 
					                        title="View: {{ item.title }}">{{ item.title }}</a></td>
 | 
				
			||||||
                <td>{{ item.author.get_full_name }}</td>
 | 
					                <td>{{ item.author.get_full_name }}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p class="article-info">{{ news.postdate|date }} - {{ news.author.get_full_name }}</p>
 | 
					    <p class="article-info">{{ news.postdate|date:"Y-m-d" }} - {{ news.author.get_full_name }}</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="article-content" itemprop="articleBody">{{ news.html }}</div>
 | 
					    <div class="article-content" itemprop="articleBody">{{ news.html }}</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@
 | 
				
			|||||||
                <th>Multilib Version</th>
 | 
					                <th>Multilib Version</th>
 | 
				
			||||||
                <th>x86_64 Version</th>
 | 
					                <th>x86_64 Version</th>
 | 
				
			||||||
                <th>x86_64 Name</th>
 | 
					                <th>x86_64 Name</th>
 | 
				
			||||||
                <th>x864_ Repo</th>
 | 
					                <th>x86_64 Repo</th>
 | 
				
			||||||
                <th>Multilib Last Updated</th>
 | 
					                <th>Multilib Last Updated</th>
 | 
				
			||||||
                <th>x86_64 Last Updated</th>
 | 
					                <th>x86_64 Last Updated</th>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
@@ -29,8 +29,8 @@
 | 
				
			|||||||
                <td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td>
 | 
					                <td><span{% if pkg2.flag_date %} class="flagged"{% endif %}>{{ pkg2.full_version }}</span></td>
 | 
				
			||||||
                <td>{% pkg_details_link pkg2 %}</td>
 | 
					                <td>{% pkg_details_link pkg2 %}</td>
 | 
				
			||||||
                <td>{{ pkg2.repo }}</td>
 | 
					                <td>{{ pkg2.repo }}</td>
 | 
				
			||||||
                <td>{{ pkg1.last_update|date }}</td>
 | 
					                <td>{{ pkg1.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ pkg2.last_update|date }}</td>
 | 
					                <td>{{ pkg2.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
{% extends "base.html" %}
 | 
					{% extends "base.html" %}
 | 
				
			||||||
{% load package_extras %}
 | 
					{% load package_extras %}
 | 
				
			||||||
{% load humanize %}
 | 
					{% load humanize %}
 | 
				
			||||||
 | 
					{% load details_link %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block title %}Arch Linux - Flag Package - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %}
 | 
					{% block title %}Arch Linux - Flag Package - {{ package.pkgname }} {{ package.full_version }} ({{ package.arch.name }}){% endblock %}
 | 
				
			||||||
{% block head %}<meta name="robots" content="noindex"/>{% endblock %}
 | 
					{% block head %}<meta name="robots" content="noindex"/>{% endblock %}
 | 
				
			||||||
@@ -29,9 +30,9 @@
 | 
				
			|||||||
    with your additional text.</p>
 | 
					    with your additional text.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p><strong>Note:</strong> Do <em>not</em> use this facility if the
 | 
					    <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!
 | 
					    package is broken! The package will be unflagged and the report will be ignored! File an issue on
 | 
				
			||||||
    <a href="https://bugs.archlinux.org/" title="Arch Linux Bugtracker">Use the
 | 
					    <a href="{% bugs_list package %}" title="Bug tickets for {{ package }}">the package's GitLab repository</a>
 | 
				
			||||||
    bugtracker to file a bug</a> instead.</p>
 | 
					    instead.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p>Please confirm your flag request for {{package.pkgname}}:</p>
 | 
					    <p>Please confirm your flag request for {{package.pkgname}}:</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 %}
 | 
				
			||||||
@@ -123,7 +123,7 @@
 | 
				
			|||||||
            <th>Description:</th>
 | 
					            <th>Description:</th>
 | 
				
			||||||
            <td class="wrap" itemprop="description">{{ pkg.pkgdesc|default:"" }}</td>
 | 
					            <td class="wrap" itemprop="description">{{ pkg.pkgdesc|default:"" }}</td>
 | 
				
			||||||
        </tr><tr>
 | 
					        </tr><tr>
 | 
				
			||||||
            <th>Upstream URL:</th>
 | 
					            <th>Homepage:</th>
 | 
				
			||||||
            <td>{% if pkg.url %}<a itemprop="url" href="{{ pkg.url }}"
 | 
					            <td>{% if pkg.url %}<a itemprop="url" href="{{ pkg.url }}"
 | 
				
			||||||
                    title="Visit the website for {{ pkg.pkgname }}">{{ pkg.url|url_unquote }}</a>{% endif %}</td>
 | 
					                    title="Visit the website for {{ pkg.pkgname }}">{{ pkg.url|url_unquote }}</a>{% endif %}</td>
 | 
				
			||||||
        </tr><tr>
 | 
					        </tr><tr>
 | 
				
			||||||
@@ -188,19 +188,19 @@
 | 
				
			|||||||
            {% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
 | 
					            {% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
 | 
				
			||||||
        </tr><tr>
 | 
					        </tr><tr>
 | 
				
			||||||
            <th>Build Date:</th>
 | 
					            <th>Build Date:</th>
 | 
				
			||||||
            <td>{{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC</td>
 | 
					            <td>{{ pkg.build_date|date:"Y-m-d H:i" }} UTC</td>
 | 
				
			||||||
        </tr>{% if pkg.signature %}<tr>
 | 
					        </tr>{% if pkg.signature %}<tr>
 | 
				
			||||||
            <th>Signed By:</th>
 | 
					            <th>Signed By:</th>
 | 
				
			||||||
            <td>{% with signer=pkg.signer %}{% if signer %}{% pgp_key_link pkg.signature.key_id signer.get_full_name|safe %}{% else %}Unknown ({% pgp_key_link pkg.signature.key_id|safe %}){% endif %}{% endwith %}</td>
 | 
					            <td>{% with signer=pkg.signer %}{% if signer %}{% pgp_key_link pkg.signature.key_id signer.get_full_name|safe %}{% else %}Unknown ({% pgp_key_link pkg.signature.key_id|safe %}){% endif %}{% endwith %}</td>
 | 
				
			||||||
        </tr><tr>
 | 
					        </tr><tr>
 | 
				
			||||||
            <th>Signature Date:</th>
 | 
					            <th>Signature Date:</th>
 | 
				
			||||||
            <td>{{ pkg.signature.creation_time|date:"DATETIME_FORMAT" }} UTC</td>
 | 
					            <td>{{ pkg.signature.creation_time|date:"Y-m-d H:i" }} UTC</td>
 | 
				
			||||||
        </tr>{% else %}<tr>
 | 
					        </tr>{% else %}<tr>
 | 
				
			||||||
            <th>Signed By:</th>
 | 
					            <th>Signed By:</th>
 | 
				
			||||||
            <td>Unsigned</td>
 | 
					            <td>Unsigned</td>
 | 
				
			||||||
        </tr>{% endif %}<tr>
 | 
					        </tr>{% endif %}<tr>
 | 
				
			||||||
            <th>Last Updated:</th>
 | 
					            <th>Last Updated:</th>
 | 
				
			||||||
            <td>{{ pkg.last_update|date:"DATETIME_FORMAT" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td>
 | 
					            <td>{{ pkg.last_update|date:"Y-m-d H:i" }} UTC{% if pkg.is_recent %} <span class="recent" title="Your mirror may not yet have this package version">({{ pkg.last_update|naturaltime }})</span>{% endif %}</td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
        {% if user.is_authenticated %}<tr>
 | 
					        {% if user.is_authenticated %}<tr>
 | 
				
			||||||
            <th>Reproducible Status:</th>
 | 
					            <th>Reproducible Status:</th>
 | 
				
			||||||
@@ -213,7 +213,7 @@
 | 
				
			|||||||
	{% endif %}
 | 
						{% endif %}
 | 
				
			||||||
        {% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr>
 | 
					        {% if user.is_authenticated %}{% with flag_request=pkg.flag_request %}{% if flag_request %}<tr>
 | 
				
			||||||
            <th>Last Flag Request:</th>
 | 
					            <th>Last Flag Request:</th>
 | 
				
			||||||
            <td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date }}:<br/>
 | 
					            <td class="wrap">From {{ flag_request.who }} on {{ flag_request.created|date:"Y-m-d" }}:<br/>
 | 
				
			||||||
                <div class="userdata">{{ flag_request.message|linebreaksbr|default:"{no message}" }}</div></td>
 | 
					                <div class="userdata">{{ flag_request.message|linebreaksbr|default:"{no message}" }}</div></td>
 | 
				
			||||||
        </tr>{% endif %}{% endwith %}{% endif %}
 | 
					        </tr>{% endif %}{% endwith %}{% endif %}
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,8 +33,8 @@
 | 
				
			|||||||
                <td>{{ pkg.full_version }}</td>
 | 
					                <td>{{ pkg.full_version }}</td>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
					                <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
				
			||||||
                <td>{{ pkg.last_update|date }}</td>
 | 
					                <td>{{ pkg.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ pkg.flag_date|date }}</td>
 | 
					                <td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@
 | 
				
			|||||||
            {% for pkg in exact_matches %}
 | 
					            {% for pkg in exact_matches %}
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td>{{ pkg.arch.name }}</td>
 | 
					                <td>{{ pkg.arch.name }}</td>
 | 
				
			||||||
                <td>{{ pkg.repo.name|capfirst }}</td>
 | 
					                <td>{{ pkg.repo.name }}</td>
 | 
				
			||||||
                <td>{% pkg_details_link pkg %}</td>
 | 
					                <td>{% pkg_details_link pkg %}</td>
 | 
				
			||||||
                {% if pkg.flag_date %}
 | 
					                {% if pkg.flag_date %}
 | 
				
			||||||
                <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
 | 
					                <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
 | 
				
			||||||
@@ -70,8 +70,8 @@
 | 
				
			|||||||
                <td>{{ pkg.full_version }}</td>
 | 
					                <td>{{ pkg.full_version }}</td>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
					                <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
				
			||||||
                <td>{{ pkg.last_update|date }}</td>
 | 
					                <td>{{ pkg.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ pkg.flag_date|date }}</td>
 | 
					                <td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
@@ -109,7 +109,7 @@
 | 
				
			|||||||
                    <td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td>
 | 
					                    <td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                    <td>{{ pkg.arch.name }}</td>
 | 
					                    <td>{{ pkg.arch.name }}</td>
 | 
				
			||||||
                    <td>{{ pkg.repo.name|capfirst }}</td>
 | 
					                    <td>{{ pkg.repo.name }}</td>
 | 
				
			||||||
                    <td>{% pkg_details_link pkg %}</td>
 | 
					                    <td>{% pkg_details_link pkg %}</td>
 | 
				
			||||||
                    {% if pkg.flag_date %}
 | 
					                    {% if pkg.flag_date %}
 | 
				
			||||||
                    <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
 | 
					                    <td><span class="flagged" title="Flagged out-of-date">{{ pkg.full_version }}</span></td>
 | 
				
			||||||
@@ -117,8 +117,8 @@
 | 
				
			|||||||
                    <td>{{ pkg.full_version }}</td>
 | 
					                    <td>{{ pkg.full_version }}</td>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                    <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
					                    <td class="wrap">{{ pkg.pkgdesc }}</td>
 | 
				
			||||||
                    <td>{{ pkg.last_update|date }}</td>
 | 
					                    <td>{{ pkg.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                    <td>{{ pkg.flag_date|date }}</td>
 | 
					                    <td>{{ pkg.flag_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                {% empty %}
 | 
					                {% empty %}
 | 
				
			||||||
                <tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr>
 | 
					                <tr class="empty"><td colspan="{% if perms.main.change_package %}8{% else %}7{% endif %}"><em>No matching packages found</em></td></tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,10 @@
 | 
				
			|||||||
            <div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label>
 | 
					            <div><label for="id_repo_{{ repo_name|lower }}" title="Target Repository {{ repo_name }}">[{{ repo_name|lower }}]</label>
 | 
				
			||||||
                <input type="checkbox" name="repo_{{ repo_name|lower }}" id="id_repo_{{ repo_name|lower }}" class="repo_filter" value="{{ repo_name|lower }}" checked="checked"/></div>
 | 
					                <input type="checkbox" name="repo_{{ repo_name|lower }}" id="id_repo_{{ repo_name|lower }}" class="repo_filter" value="{{ repo_name|lower }}" checked="checked"/></div>
 | 
				
			||||||
            {% endfor %}
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					            {% 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>
 | 
					            <div><label for="id_pending" title="Packages with not enough signoffs">Only Pending Approval</label>
 | 
				
			||||||
                <input type="checkbox" name="pending" id="id_pending" value="pending"/></div>
 | 
					                <input type="checkbox" name="pending" id="id_pending" value="pending"/></div>
 | 
				
			||||||
            <div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div>
 | 
					            <div><label> </label><input title="Reset search criteria" type="button" id="criteria_reset" value="Reset"/></div>
 | 
				
			||||||
@@ -50,13 +54,14 @@
 | 
				
			|||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody id="tbody_signoffs">
 | 
					        <tbody id="tbody_signoffs">
 | 
				
			||||||
            {% for group in signoff_groups %}<tr class="{{ group.arch.name }} {{ group.target_repo|lower }}">
 | 
					            {% for group in signoff_groups %}
 | 
				
			||||||
 | 
					            <tr class="{% if user == group.packager %} mine{% endif %} {{ group.arch.name }} {{ group.target_repo|lower }}">
 | 
				
			||||||
                <td>{% pkg_details_link group.package %} {{ group.version }}</td>
 | 
					                <td>{% pkg_details_link group.package %} {{ group.version }}</td>
 | 
				
			||||||
                <td>{{ group.arch.name }}</td>
 | 
					                <td>{{ group.arch.name }}</td>
 | 
				
			||||||
                <td>{{ group.target_repo }}</td>
 | 
					                <td>{{ group.target_repo }}</td>
 | 
				
			||||||
                <td>{{ group.packager|default:"Unknown" }}</td>
 | 
					                <td>{{ group.packager|default:"Unknown" }}</td>
 | 
				
			||||||
                <td>{{ group.packages|length }}</td>
 | 
					                <td>{{ group.packages|length }}</td>
 | 
				
			||||||
                <td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date }}</td>
 | 
					                <td class="epoch-{{ group.last_update|date:'U' }}">{{ group.last_update|date:"Y-m-d" }}</td>
 | 
				
			||||||
                {% if group.specification.known_bad %}
 | 
					                {% if group.specification.known_bad %}
 | 
				
			||||||
                <td class="approval signoff-bad">Bad</td>
 | 
					                <td class="approval signoff-bad">Bad</td>
 | 
				
			||||||
                {% else %}
 | 
					                {% else %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@
 | 
				
			|||||||
        <a href="{{ entry.url }}"
 | 
					        <a href="{{ entry.url }}"
 | 
				
			||||||
            title="View full article: {{ entry.title }}">{{ entry.title }}</a>
 | 
					            title="View full article: {{ entry.title }}">{{ entry.title }}</a>
 | 
				
			||||||
    </h4>
 | 
					    </h4>
 | 
				
			||||||
    <p class="timestamp">{{ entry.publishdate|date }}</p>
 | 
					    <p class="timestamp">{{ entry.publishdate|date:"Y-m-d" }}</p>
 | 
				
			||||||
    <div class="article-content">
 | 
					    <div class="article-content">
 | 
				
			||||||
        {{ entry.summary |safe }}
 | 
					        {{ entry.summary |safe }}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,11 @@
 | 
				
			|||||||
                    <td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}"
 | 
					                    <td>{% if prof.website %}<a itemprop="url" href="{{ prof.website }}"
 | 
				
			||||||
                            title="Visit the website for {{ dev.get_full_name }}">
 | 
					                            title="Visit the website for {{ dev.get_full_name }}">
 | 
				
			||||||
                            {{ prof.website }}</a>{% endif %}</td>
 | 
					                            {{ prof.website }}</a>{% endif %}</td>
 | 
				
			||||||
 | 
					                </tr><tr>
 | 
				
			||||||
 | 
					                    <th>Social:</th>
 | 
				
			||||||
 | 
					                    <td>{% if prof.social %}<a itemprop="url" href="{{ prof.social }}"
 | 
				
			||||||
 | 
					                            title="Visit social account for {{ dev.get_full_name }}" rel="me">
 | 
				
			||||||
 | 
					                            {{ prof.social }}</a>{% endif %}</td>
 | 
				
			||||||
                </tr><tr>
 | 
					                </tr><tr>
 | 
				
			||||||
                    <th>Occupation:</th>
 | 
					                    <th>Occupation:</th>
 | 
				
			||||||
                    <td>{{ prof.occupation }}</td>
 | 
					                    <td>{{ prof.occupation }}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,17 +53,6 @@
 | 
				
			|||||||
    <img src="{% static "nitrokey_logo.png" %}"
 | 
					    <img src="{% static "nitrokey_logo.png" %}"
 | 
				
			||||||
    class="sponsor-btn-nitrokey" title="" alt="Nitrokey logo"/></a>
 | 
					    class="sponsor-btn-nitrokey" title="" alt="Nitrokey logo"/></a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p>We would also like to thank <a href="https://www.privateinternetaccess.com/"
 | 
					 | 
				
			||||||
    title="Private Internet Access">Private Internet Access</a> for sponsoring
 | 
					 | 
				
			||||||
    dedicated servers across the globe. Private Internet Access is the leading
 | 
					 | 
				
			||||||
    VPN Service provider specializing in secure, encrypted VPN tunnels which
 | 
					 | 
				
			||||||
    create several layers of privacy and security providing users safety on the
 | 
					 | 
				
			||||||
    internet.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <a href="https://www.privateinternetaccess.com/" title="Private Internet Access">
 | 
					 | 
				
			||||||
    <img src="{% static "pia_logo.png" %}"
 | 
					 | 
				
			||||||
    class="sponsor-btn-pia" title="" alt="Private Internet Access logo"/></a>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <p>We would also like to thank <a href="https://www.shells.com/"
 | 
					    <p>We would also like to thank <a href="https://www.shells.com/"
 | 
				
			||||||
    title="Shells">Shells.com</a> for their monetary donation.
 | 
					    title="Shells">Shells.com</a> for their monetary donation.
 | 
				
			||||||
    Shells provides you with a 1-click, powerful virtual desktop environment,
 | 
					    Shells provides you with a 1-click, powerful virtual desktop environment,
 | 
				
			||||||
@@ -74,16 +63,10 @@
 | 
				
			|||||||
    <img src="{% static "shells_logo.png" %}"
 | 
					    <img src="{% static "shells_logo.png" %}"
 | 
				
			||||||
    title="" alt="Shells logo"/></a>
 | 
					    title="" alt="Shells logo"/></a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p>We would also like to thank <a href="https://uptimerobot.com/"
 | 
					 | 
				
			||||||
    title="UptimeRobot">UptimeRobot</a> for providing their monitoring service to us.
 | 
					 | 
				
			||||||
    UptimeRobot is a leading uptime monitoring service.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <a href="https://uptimerobot.com/" title="UptimeRobot">
 | 
					 | 
				
			||||||
    <img src="{% static "uptimerobot_logo.png" %}"
 | 
					 | 
				
			||||||
    title="" alt="UptimeRobot logo"/></a>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <h3>Past donors</h3>
 | 
					    <h3>Past donors</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> & <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div id="donor-list">
 | 
					    <div id="donor-list">
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
{% for donor in donors %}
 | 
					{% for donor in donors %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,8 @@
 | 
				
			|||||||
    It is intended for new installations only; an existing Arch Linux system
 | 
					    It is intended for new installations only; an existing Arch Linux system
 | 
				
			||||||
    can always be updated with <code>pacman -Syu</code>.</p>
 | 
					    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>
 | 
					    <ul>
 | 
				
			||||||
        {% if release.version %}<li><strong>Current Release:</strong> {{ release.version }}</li>{% endif %}
 | 
					        {% 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 %}
 | 
					        {% 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
 | 
					    to update your existing system. You may be looking for
 | 
				
			||||||
    <a href="{% url 'mirrorlist' %}">an updated mirrorlist</a> instead.</p>
 | 
					    <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
 | 
					    <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.
 | 
					    download is finished, so you can seed it back to others.
 | 
				
			||||||
@@ -75,13 +77,6 @@
 | 
				
			|||||||
            title="Arch Linux Netboot">Arch Linux Netboot</a></li>
 | 
					            title="Arch Linux Netboot">Arch Linux Netboot</a></li>
 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <h3>Vagrant images</h3>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <p>Vagrant images for libvirt and virtualbox are available on the <a href="https://app.vagrantup.com/archlinux/boxes/archlinux">Vagrant Cloud</a>. You can bootstrap the image with the following commands:</p>
 | 
					 | 
				
			||||||
    <code>vagrant init archlinux/archlinux</code>
 | 
					 | 
				
			||||||
    <br/>
 | 
					 | 
				
			||||||
    <code>vagrant up</code>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <h3>Docker image</h3>
 | 
					    <h3>Docker image</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p>The official Docker image is available on <a href="https://hub.docker.com/_/archlinux/">Docker Hub</a>. You can run the image with the following command:</p>
 | 
					    <p>The official Docker image is available on <a href="https://hub.docker.com/_/archlinux/">Docker Hub</a>. You can run the image with the following command:</p>
 | 
				
			||||||
@@ -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>
 | 
					    <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
 | 
					    <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>
 | 
					    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>
 | 
					    <h4 id="checksums">Checksums and signatures</h4>
 | 
				
			||||||
@@ -130,15 +133,15 @@
 | 
				
			|||||||
    <pre><code>$ b2sum -c b2sums.txt</code></pre>
 | 
					    <pre><code>$ b2sum -c b2sums.txt</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    To verify the PGP signature using Sequoia, first download the release signing key from WKD:
 | 
					    To verify the PGP signature using Sequoia, first download the release signing key from WKD:
 | 
				
			||||||
    <pre><code>$ sq network wkd fetch {{ release.wkd_email }} -o release-key.pgp</code></pre>
 | 
					    <pre><code>$ sq network wkd search {{ release.wkd_email }} --output release-key.pgp</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    With this signing key, verify the signature:
 | 
					    With this signing key, verify the signature:
 | 
				
			||||||
    <pre><code>$ sq verify --signer-file release-key.pgp --detached archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
 | 
					    <pre><code>$ sq verify --signer-file release-key.pgp --signature-file archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Alternatively, using GnuPG, download the signing key from WKD:
 | 
					    Alternatively, using GnuPG, download the signing key from WKD:
 | 
				
			||||||
    <pre><code>$ gpg --auto-key-locate clear,wkd -v --locate-external-key {{ release.wkd_email }}</code></pre>
 | 
					    <pre><code>$ gpg --auto-key-locate clear,wkd -v --locate-external-key {{ release.wkd_email }}</code></pre>
 | 
				
			||||||
    Verify the signature:
 | 
					    Verify the signature:
 | 
				
			||||||
    <pre><code>$ gpg --keyserver-options auto-key-retrieve --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
 | 
					    <pre><code>$ gpg --verify archlinux-{{ release.version }}-x86_64.iso.sig archlinux-{{ release.version }}-x86_64.iso</code></pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {% cache 600 download-mirrors %}
 | 
					    {% cache 600 download-mirrors %}
 | 
				
			||||||
    <div id="download-mirrors">
 | 
					    <div id="download-mirrors">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@
 | 
				
			|||||||
        <a href="{{ news.get_absolute_url }}"
 | 
					        <a href="{{ news.get_absolute_url }}"
 | 
				
			||||||
            title="View full article: {{ news.title }}">{{ news.title }}</a>
 | 
					            title="View full article: {{ news.title }}">{{ news.title }}</a>
 | 
				
			||||||
    </h4>
 | 
					    </h4>
 | 
				
			||||||
    <p class="timestamp">{{ news.postdate|date }}</p>
 | 
					    <p class="timestamp">{{ news.postdate|date:"Y-m-d" }}</p>
 | 
				
			||||||
    <div class="article-content">
 | 
					    <div class="article-content">
 | 
				
			||||||
        {% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }}
 | 
					        {% if forloop.counter0 == 0 %}{{ news.html|truncatewords_html:300 }}
 | 
				
			||||||
        {% else %}{{ news.html|truncatewords_html:100 }}{% endif %}
 | 
					        {% else %}{{ news.html|truncatewords_html:100 }}{% endif %}
 | 
				
			||||||
@@ -63,7 +63,7 @@
 | 
				
			|||||||
    </h3>
 | 
					    </h3>
 | 
				
			||||||
    <dl class="newslist">
 | 
					    <dl class="newslist">
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
        <dt>{{ news.postdate|date }}</dt>
 | 
					        <dt>{{ news.postdate|date:"Y-m-d" }}</dt>
 | 
				
			||||||
        <dd>
 | 
					        <dd>
 | 
				
			||||||
            <a href="{{ news.get_absolute_url }}"
 | 
					            <a href="{{ news.get_absolute_url }}"
 | 
				
			||||||
                title="View full article: {{ news.title }}">{{ news.title }}</a>
 | 
					                title="View full article: {{ news.title }}">{{ news.title }}</a>
 | 
				
			||||||
@@ -133,8 +133,6 @@
 | 
				
			|||||||
    <h4>Support</h4>
 | 
					    <h4>Support</h4>
 | 
				
			||||||
    <ul>
 | 
					    <ul>
 | 
				
			||||||
        <li><a href="{% url 'page-donate' %}" title="Help support Arch Linux">Donate</a></li>
 | 
					        <li><a href="{% url 'page-donate' %}" title="Help support Arch Linux">Donate</a></li>
 | 
				
			||||||
        <li><a href="https://www.unixstickers.com/tag/archlinux" title="Arch
 | 
					 | 
				
			||||||
	Linux stickers, t-shirts, hoodies, mugs, posters and pins">Products via Unixstickers</a></li>
 | 
					 | 
				
			||||||
        <li><a href="https://www.freewear.org/?page=list_items&org=Archlinux"
 | 
					        <li><a href="https://www.freewear.org/?page=list_items&org=Archlinux"
 | 
				
			||||||
            title="T-shirts">T-shirts via Freewear</a></li>
 | 
					            title="T-shirts">T-shirts via Freewear</a></li>
 | 
				
			||||||
        <li><a href="https://www.hellotux.com/arch"
 | 
					        <li><a href="https://www.hellotux.com/arch"
 | 
				
			||||||
@@ -202,11 +200,6 @@
 | 
				
			|||||||
            title="" alt="Hetzner logo"/>
 | 
					            title="" alt="Hetzner logo"/>
 | 
				
			||||||
    </a>
 | 
					    </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">
 | 
					    <a href="https://icons8.com/" title="Icons8">
 | 
				
			||||||
        <img src="{% static "icons8_logo.png" %}"
 | 
					        <img src="{% static "icons8_logo.png" %}"
 | 
				
			||||||
            title="" alt="Icons8 logo"/>
 | 
					            title="" alt="Icons8 logo"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
    <h2>{{ release.version }}</h2>
 | 
					    <h2>{{ release.version }}</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ul>
 | 
					    <ul>
 | 
				
			||||||
        <li><strong>Release Date:</strong> {{ release.release_date|date }}</li>
 | 
					        <li><strong>Release Date:</strong> {{ release.release_date|date:"Y-m-d" }}</li>
 | 
				
			||||||
        {% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %}
 | 
					        {% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %}
 | 
				
			||||||
        <li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li>
 | 
					        <li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li>
 | 
				
			||||||
        {% if release.torrent_data %}
 | 
					        {% if release.torrent_data %}
 | 
				
			||||||
@@ -38,7 +38,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <ul>
 | 
					    <ul>
 | 
				
			||||||
        <li><strong>Comment:</strong> {{ torrent.comment }}</li>
 | 
					        <li><strong>Comment:</strong> {{ torrent.comment }}</li>
 | 
				
			||||||
        <li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"DATETIME_FORMAT" }} UTC</li>
 | 
					        <li><strong>Creation Date:</strong> {{ torrent.creation_date|date:"Y-m-d H:i" }} UTC</li>
 | 
				
			||||||
        <li><strong>Created By:</strong> {{ torrent.created_by }}</li>
 | 
					        <li><strong>Created By:</strong> {{ torrent.created_by }}</li>
 | 
				
			||||||
        <li><strong>Announce URL:</strong> {{ torrent.announce }}</li>
 | 
					        <li><strong>Announce URL:</strong> {{ torrent.announce }}</li>
 | 
				
			||||||
        <li><strong>File Name:</strong> {{ torrent.file_name }}</li>
 | 
					        <li><strong>File Name:</strong> {{ torrent.file_name }}</li>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@
 | 
				
			|||||||
                    <a href="{{ item.magnet_uri }}"
 | 
					                    <a href="{{ item.magnet_uri }}"
 | 
				
			||||||
                       title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a>
 | 
					                       title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a>
 | 
				
			||||||
                {% endif %}</td>
 | 
					                {% endif %}</td>
 | 
				
			||||||
                <td>{{ item.release_date|date }}</td>
 | 
					                <td>{{ item.release_date|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td>
 | 
					                <td><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td>
 | 
				
			||||||
                <td>{{ item.kernel_version|default:"" }}</td>
 | 
					                <td>{{ item.kernel_version|default:"" }}</td>
 | 
				
			||||||
                <td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td>
 | 
					                <td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@
 | 
				
			|||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td class="wrap"><a href="{{ list.get_absolute_url }}"
 | 
					                <td class="wrap"><a href="{{ list.get_absolute_url }}"
 | 
				
			||||||
                        title="View todo list: {{ list.name }}">{{ list.name }}</a></td>
 | 
					                        title="View todo list: {{ list.name }}">{{ list.name }}</a></td>
 | 
				
			||||||
                <td>{{ list.created|date }}</td>
 | 
					                <td>{{ list.created|date:"Y-m-d" }}</td>
 | 
				
			||||||
                <td>{{ list.creator.get_full_name }}</td>
 | 
					                <td>{{ list.creator.get_full_name }}</td>
 | 
				
			||||||
                <td>{{ list.pkg_count }}</td>
 | 
					                <td>{{ list.pkg_count }}</td>
 | 
				
			||||||
                <td>{{ list.incomplete_count }}</td>
 | 
					                <td>{{ list.incomplete_count }}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||