Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
8e6bc69713 | |||
![]() |
f624f5677b | ||
![]() |
2064099696 | ||
![]() |
67209075c5 | ||
![]() |
336d686ca2 | ||
![]() |
4f0e24f1f7 | ||
7bf65b63ff
|
|||
188ead820d | |||
![]() |
e07054c8ea | ||
![]() |
97aae09dce | ||
![]() |
0ce1a0ea5f | ||
![]() |
f38770be76 | ||
![]() |
2d39dc6379 | ||
![]() |
df4b0bfd67 | ||
![]() |
5da7fa80c5 | ||
![]() |
8d495d4fa7 | ||
![]() |
796c3f410f | ||
![]() |
37687bf9e4 | ||
7accacd5fd | |||
10fecc63e9
|
|||
368e248efc | |||
a71b1f783e
|
2
.flake8
2
.flake8
@@ -1,3 +1,3 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 300
|
max-line-length = 118
|
||||||
ignore = E731, E241, E741
|
ignore = E731, E241, E741
|
||||||
|
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
@@ -9,14 +9,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python 3.11
|
- name: Set up Python 3.13
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.13
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt && pip install -r requirements_test.txt
|
pip install -r requirements.txt && pip install -r requirements_test.txt && pip install ruff
|
||||||
|
- name: Run ruff
|
||||||
|
run: |
|
||||||
|
ruff check .
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
run: |
|
run: |
|
||||||
make lint
|
make lint
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.12-alpine3.20 AS base
|
FROM python:3.13-alpine3.20 AS base
|
||||||
|
|
||||||
RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
|
RUN apk add --no-cache git gcc musl-dev curl gpg gpg-agent
|
||||||
|
|
||||||
|
@@ -131,7 +131,7 @@ Archweb provides multiple management commands for importing various sorts of dat
|
|||||||
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
|
* reporead_inotify - Watches a templated patch for updates of *.files.tar.gz to update Arch databases with.
|
||||||
* donor_import - Import a single donator from a mail passed to stdin
|
* donor_import - Import a single donator from a mail passed to stdin
|
||||||
* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
|
* mirrorcheck - Poll every active mirror URLs to store the lastsnyc time and record network timing details.
|
||||||
* mirrorresolv - Poll every active mirror URLs and determine wheteher they have IP4 and/or IPv6 addresses.
|
* mirrorresolv - Poll every active mirror URLs and determine whether they have IP4 and/or IPv6 addresses.
|
||||||
* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
|
* populate_signoffs - retrieves the latest commit message of a signoff-eligible package.
|
||||||
* update_planet - Import all feeds for users who have a valid website and website_rss in their user profile.
|
* update_planet - Import all feeds for users who have a valid website and website_rss in their user profile.
|
||||||
* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.
|
* read_links - Reads a repo.links.db.tar.gz file and updates the Soname model.
|
||||||
|
@@ -71,7 +71,8 @@ class Database(object):
|
|||||||
retry = False
|
retry = False
|
||||||
except OperationalError as exc:
|
except OperationalError as exc:
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
logger.error('Unable to update database \'%s\', retrying=%d', self.path, retry_count, exc_info=exc)
|
logger.error('Unable to update database \'%s\', retrying=%d',
|
||||||
|
self.path, retry_count, exc_info=exc)
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
if retry_count == self.retry_limit:
|
if retry_count == self.retry_limit:
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@@ -202,7 +202,8 @@ def non_existing_dependencies(packages):
|
|||||||
|
|
||||||
|
|
||||||
def non_reproducible_packages(packages):
|
def non_reproducible_packages(packages):
|
||||||
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname'))
|
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD,
|
||||||
|
pkg__pkgname__in=packages.values('pkgname'))
|
||||||
return linkify_non_reproducible_packages(statuses)
|
return linkify_non_reproducible_packages(statuses)
|
||||||
|
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ def orphan_dependencies(packages):
|
|||||||
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
|
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
|
||||||
JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
|
JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
|
||||||
ORDER BY child.pkgname;
|
ORDER BY child.pkgname;
|
||||||
"""
|
""" # noqa: E501
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
|
|
||||||
for row in cursor.fetchall():
|
for row in cursor.fetchall():
|
||||||
|
@@ -216,7 +216,9 @@ def tier0_mirror_auth(request):
|
|||||||
token = credentials[1]
|
token = credentials[1]
|
||||||
|
|
||||||
groups = Group.objects.filter(name__in=SELECTED_GROUPS)
|
groups = Group.objects.filter(name__in=SELECTED_GROUPS)
|
||||||
user = User.objects.filter(username=username, is_active=True, groups__in=groups).select_related('userprofile').first()
|
user = User.objects.filter(username=username,
|
||||||
|
is_active=True,
|
||||||
|
groups__in=groups).select_related('userprofile').first()
|
||||||
if not user:
|
if not user:
|
||||||
return unauthorized
|
return unauthorized
|
||||||
|
|
||||||
|
@@ -42,4 +42,4 @@ services:
|
|||||||
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
|
||||||
|
|
||||||
|
@@ -280,7 +280,8 @@ class Package(models.Model):
|
|||||||
dep_pkgs = list(dep_pkgs)
|
dep_pkgs = list(dep_pkgs)
|
||||||
dep = dep_pkgs[0]
|
dep = dep_pkgs[0]
|
||||||
if len(dep_pkgs) > 1:
|
if len(dep_pkgs) > 1:
|
||||||
dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
|
dep_pkgs = [d for d in dep_pkgs
|
||||||
|
if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
|
||||||
if len(dep_pkgs) > 0:
|
if len(dep_pkgs) > 0:
|
||||||
dep = dep_pkgs[0]
|
dep = dep_pkgs[0]
|
||||||
trimmed.append(dep)
|
trimmed.append(dep)
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import cssmin
|
|
||||||
import jsmin
|
import jsmin
|
||||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
|
import cssmin
|
||||||
|
|
||||||
|
|
||||||
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
|
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
|
||||||
"""
|
"""
|
||||||
|
@@ -10,7 +10,7 @@ def format_key(key_id):
|
|||||||
if len(key_id) in (8, 20):
|
if len(key_id) in (8, 20):
|
||||||
return '0x%s' % key_id
|
return '0x%s' % key_id
|
||||||
elif len(key_id) == 40:
|
elif len(key_id) == 40:
|
||||||
# normal display format is 5 groups of 4 hex chars seperated by spaces,
|
# normal display format is 5 groups of 4 hex chars separated by spaces,
|
||||||
# double space, then 5 more groups of 4 hex chars
|
# double space, then 5 more groups of 4 hex chars
|
||||||
split = tuple(key_id[i:i + 4] for i in range(0, 40, 4))
|
split = tuple(key_id[i:i + 4] for i in range(0, 40, 4))
|
||||||
return '%s\u00a0 %s' % (' '.join(split[0:5]), ' '.join(split[5:10]))
|
return '%s\u00a0 %s' % (' '.join(split[0:5]), ' '.join(split[5:10]))
|
||||||
@@ -35,7 +35,7 @@ def pgp_dev_key_link(key_id):
|
|||||||
key_id = pad_key_id(key_id)
|
key_id = pad_key_id(key_id)
|
||||||
if not key_id:
|
if not key_id:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
link_text = (''.join((f'<span>{key_id[i:i+4]}</span>' for i in range(0, len(key_id), 4))))
|
link_text = (''.join((f'<span>{key_id[i:i + 4]}</span>' for i in range(0, len(key_id), 4))))
|
||||||
link_text = f'<div class="pgp-key-ids">{link_text}</div>'
|
link_text = f'<div class="pgp-key-ids">{link_text}</div>'
|
||||||
return pgp_key_link(key_id, link_text)
|
return pgp_key_link(key_id, link_text)
|
||||||
|
|
||||||
@@ -51,7 +51,9 @@ def pgp_key_link(key_id, link_text=None):
|
|||||||
return format_key(key_id)
|
return format_key(key_id)
|
||||||
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
|
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
|
||||||
scheme = 'https' if pgp_server_secure else 'http'
|
scheme = 'https' if pgp_server_secure else 'http'
|
||||||
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme, pgp_server, key_id)
|
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme,
|
||||||
|
pgp_server,
|
||||||
|
key_id)
|
||||||
if link_text is None:
|
if link_text is None:
|
||||||
link_text = '0x%s' % key_id[-8:]
|
link_text = '0x%s' % key_id[-8:]
|
||||||
values = (url, format_key(key_id), link_text)
|
values = (url, format_key(key_id), link_text)
|
||||||
|
@@ -27,7 +27,7 @@ def test_mirrorurl_get_full_url(mirrorurl):
|
|||||||
|
|
||||||
def test_mirror_url_clean(mirrorurl):
|
def test_mirror_url_clean(mirrorurl):
|
||||||
mirrorurl.clean()
|
mirrorurl.clean()
|
||||||
# TOOD(jelle): this expects HOSTNAME to resolve, maybe mock
|
# TODO(jelle): this expects HOSTNAME to resolve, maybe mock
|
||||||
assert mirrorurl.has_ipv4
|
assert mirrorurl.has_ipv4
|
||||||
# requires ipv6 on host... mock?
|
# requires ipv6 on host... mock?
|
||||||
# assert mirrorurl.has_ipv6 == True
|
# assert mirrorurl.has_ipv6 == True
|
||||||
|
@@ -54,10 +54,12 @@ class NewsCreateView(CreateView):
|
|||||||
if settings.MAILMAN_PASSWORD:
|
if settings.MAILMAN_PASSWORD:
|
||||||
headers['Approved'] = settings.MAILMAN_PASSWORD
|
headers['Approved'] = settings.MAILMAN_PASSWORD
|
||||||
template = loader.get_template('news/news_email_notification.txt')
|
template = loader.get_template('news/news_email_notification.txt')
|
||||||
|
author = newsitem.author.get_full_name()
|
||||||
|
from_ = f'"Arch Linux: Recent news updates: {author}" <{settings.ANNOUNCE_EMAIL}>'
|
||||||
EmailMessage(
|
EmailMessage(
|
||||||
subject=f'[arch-announce] {newsitem.title}',
|
subject=f'[arch-announce] {newsitem.title}',
|
||||||
body=template.render(ctx),
|
body=template.render(ctx),
|
||||||
from_email=f'"Arch Linux: Recent news updates: {newsitem.author.get_full_name()}" <{settings.ANNOUNCE_EMAIL}>',
|
from_email=from_,
|
||||||
to=[settings.ANNOUNCE_EMAIL],
|
to=[settings.ANNOUNCE_EMAIL],
|
||||||
headers=headers).send()
|
headers=headers).send()
|
||||||
return super(NewsCreateView, self).form_valid(form)
|
return super(NewsCreateView, self).form_valid(form)
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "World-Gremlins",
|
"name": "world-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Galaxy-Gremlins",
|
"name": "galaxy-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "System",
|
"name": "system",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "World",
|
"name": "world",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Galaxy",
|
"name": "galaxy",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Lib32",
|
"name": "lib32",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": false
|
"testing": false
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "Lib32-Gremlins",
|
"name": "lib32-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
@@ -89,10 +89,58 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"bugs_category": 0,
|
"bugs_category": 0,
|
||||||
"staging": false,
|
"staging": false,
|
||||||
"name": "System-Gremlins",
|
"name": "system-gremlins",
|
||||||
"bugs_project": 0,
|
"bugs_project": 0,
|
||||||
"svn_root": "packages",
|
"svn_root": "packages",
|
||||||
"testing": true
|
"testing": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 9,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "system-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 10,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "world-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 11,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "galaxy-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pk": 12,
|
||||||
|
"model": "main.repo",
|
||||||
|
"fields": {
|
||||||
|
"bugs_category": 0,
|
||||||
|
"staging": true,
|
||||||
|
"name": "lib32-goblins",
|
||||||
|
"bugs_project": 0,
|
||||||
|
"svn_root": "packages",
|
||||||
|
"testing": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -53,7 +53,8 @@ def create_specification(package, log, finder):
|
|||||||
|
|
||||||
|
|
||||||
def get_tag_info(repo, pkgbase, version):
|
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'
|
||||||
|
|
||||||
@@ -112,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)
|
||||||
|
@@ -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="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': 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': current_site.domain,
|
||||||
}
|
}
|
||||||
return render(request, 'public/index.html', context)
|
return render(request, 'public/index.html', context)
|
||||||
|
|
||||||
@@ -92,8 +93,9 @@ def feeds(request):
|
|||||||
@cache_control(max_age=307)
|
@cache_control(max_age=307)
|
||||||
def keys(request):
|
def keys(request):
|
||||||
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
|
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
|
||||||
users = User.objects.filter(
|
users = User.objects.filter(is_active=True,
|
||||||
is_active=True, userprofile__id__in=profile_ids).order_by('first_name', 'last_name').select_related('userprofile')
|
userprofile__id__in=profile_ids).order_by('first_name',
|
||||||
|
'last_name').select_related('userprofile')
|
||||||
user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users
|
user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users
|
||||||
if user.userprofile.pgp_key)
|
if user.userprofile.pgp_key)
|
||||||
|
|
||||||
|
@@ -9,8 +9,10 @@ from .views import ReleaseDetailView, ReleaseListView
|
|||||||
releases_patterns = [
|
releases_patterns = [
|
||||||
path('', ReleaseListView.as_view(), name='releng-release-list'),
|
path('', ReleaseListView.as_view(), name='releng-release-list'),
|
||||||
path('json/', views.releases_json, name='releng-release-list-json'),
|
path('json/', views.releases_json, name='releng-release-list-json'),
|
||||||
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()), name='releng-release-detail'),
|
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()),
|
||||||
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), name='releng-release-torrent'),
|
name='releng-release-detail'),
|
||||||
|
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent),
|
||||||
|
name='releng-release-torrent'),
|
||||||
]
|
]
|
||||||
|
|
||||||
netboot_patterns = [
|
netboot_patterns = [
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
|
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
|
||||||
Django==5.0.10
|
Django==5.0.11
|
||||||
IPy==1.1
|
IPy==1.1
|
||||||
Markdown==3.3.7
|
Markdown==3.3.7
|
||||||
bencode.py==4.0.0
|
bencode.py==4.0.0
|
||||||
|
@@ -24,7 +24,6 @@ select = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # line lengt violation
|
|
||||||
"E731", # Do not assign a `lambda` expression, use a `def`
|
"E731", # Do not assign a `lambda` expression, use a `def`
|
||||||
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
|
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
|
||||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||||
@@ -34,3 +33,8 @@ ignore = [
|
|||||||
"DJ008", # Model does not define `__str__` method
|
"DJ008", # Model does not define `__str__` method
|
||||||
"DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
|
"DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
|
||||||
]
|
]
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
"*/migrations/*.py", # Ignore Django migrations
|
||||||
|
"src/cssmin/*" # cssmin, not our code
|
||||||
|
]
|
||||||
|
@@ -267,7 +267,9 @@ if DEBUG_TOOLBAR:
|
|||||||
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
|
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
|
||||||
|
|
||||||
if PROMETHEUS_METRICS:
|
if PROMETHEUS_METRICS:
|
||||||
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', *list(MIDDLEWARE), 'django_prometheus.middleware.PrometheusAfterMiddleware']
|
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware',
|
||||||
|
*list(MIDDLEWARE),
|
||||||
|
'django_prometheus.middleware.PrometheusAfterMiddleware']
|
||||||
|
|
||||||
INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
|
INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
|
||||||
|
@@ -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 %}
|
||||||
|
@@ -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>
|
||||||
@@ -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>
|
||||||
|
@@ -84,6 +84,8 @@
|
|||||||
|
|
||||||
<h3>Past donors</h3>
|
<h3>Past donors</h3>
|
||||||
|
|
||||||
|
<p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a> & <a href="https://www.loadview-testing.com/" title="LoadView">LoadView</a></p>
|
||||||
|
|
||||||
<div id="donor-list">
|
<div id="donor-list">
|
||||||
<ul>
|
<ul>
|
||||||
{% for donor in donors %}
|
{% for donor in donors %}
|
||||||
|
@@ -28,7 +28,9 @@ class Migration(migrations.Migration):
|
|||||||
('created', models.DateTimeField(db_index=True)),
|
('created', models.DateTimeField(db_index=True)),
|
||||||
('last_modified', models.DateTimeField(editable=False)),
|
('last_modified', models.DateTimeField(editable=False)),
|
||||||
('raw', models.TextField(blank=True)),
|
('raw', models.TextField(blank=True)),
|
||||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_todolists', to=settings.AUTH_USER_MODEL)),
|
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name='created_todolists',
|
||||||
|
to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'get_latest_by': 'created',
|
'get_latest_by': 'created',
|
||||||
@@ -43,13 +45,16 @@ class Migration(migrations.Migration):
|
|||||||
('created', models.DateTimeField(editable=False)),
|
('created', models.DateTimeField(editable=False)),
|
||||||
('last_modified', models.DateTimeField(editable=False)),
|
('last_modified', models.DateTimeField(editable=False)),
|
||||||
('removed', models.DateTimeField(blank=True, null=True)),
|
('removed', models.DateTimeField(blank=True, null=True)),
|
||||||
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')], default=0)),
|
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')],
|
||||||
|
default=0)),
|
||||||
('comments', models.TextField(blank=True, null=True)),
|
('comments', models.TextField(blank=True, null=True)),
|
||||||
('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')),
|
('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')),
|
||||||
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Package')),
|
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to='main.Package')),
|
||||||
('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Repo')),
|
('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Repo')),
|
||||||
('todolist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todolists.Todolist')),
|
('todolist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todolists.Todolist')),
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'get_latest_by': 'created',
|
'get_latest_by': 'created',
|
||||||
|
@@ -13,6 +13,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='todolist',
|
model_name='todolist',
|
||||||
name='kind',
|
name='kind',
|
||||||
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0, help_text='(Rebuild for soname bumps, Task for independent tasks)'),
|
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0,
|
||||||
|
help_text='(Rebuild for soname bumps, Task for independent tasks)'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -23,7 +23,8 @@ class Todolist(models.Model):
|
|||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
|
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
|
||||||
created = models.DateTimeField(db_index=True)
|
created = models.DateTimeField(db_index=True)
|
||||||
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES, help_text='(Rebuild for soname bumps, Task for independent tasks)')
|
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES,
|
||||||
|
help_text='(Rebuild for soname bumps, Task for independent tasks)')
|
||||||
last_modified = models.DateTimeField(editable=False)
|
last_modified = models.DateTimeField(editable=False)
|
||||||
raw = models.TextField(blank=True)
|
raw = models.TextField(blank=True)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user