Compare commits
12 Commits
v25.1.14
...
336d686ca2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
336d686ca2 | ||
![]() |
4f0e24f1f7 | ||
![]() |
e07054c8ea | ||
![]() |
97aae09dce | ||
![]() |
0ce1a0ea5f | ||
![]() |
f38770be76 | ||
![]() |
2d39dc6379 | ||
![]() |
df4b0bfd67 | ||
![]() |
5da7fa80c5 | ||
![]() |
8d495d4fa7 | ||
![]() |
796c3f410f | ||
![]() |
37687bf9e4 |
2
.flake8
2
.flake8
@@ -1,3 +1,3 @@
|
||||
[flake8]
|
||||
max-line-length = 300
|
||||
max-line-length = 118
|
||||
ignore = E731, E241, E741
|
||||
|
9
.github/workflows/main.yml
vendored
9
.github/workflows/main.yml
vendored
@@ -9,14 +9,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt && pip install -r requirements_test.txt
|
||||
pip install -r requirements.txt && pip install -r requirements_test.txt && pip install ruff
|
||||
- name: Run ruff
|
||||
run: |
|
||||
ruff check .
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
make lint
|
||||
|
@@ -71,7 +71,8 @@ class Database(object):
|
||||
retry = False
|
||||
except OperationalError as exc:
|
||||
retry_count += 1
|
||||
logger.error('Unable to update database \'%s\', retrying=%d', self.path, retry_count, exc_info=exc)
|
||||
logger.error('Unable to update database \'%s\', retrying=%d',
|
||||
self.path, retry_count, exc_info=exc)
|
||||
time.sleep(5)
|
||||
|
||||
if retry_count == self.retry_limit:
|
||||
|
@@ -89,7 +89,9 @@ class Command(BaseCommand):
|
||||
for name in all_paths:
|
||||
manager.add_watch(name, mask)
|
||||
|
||||
handler = EventHandler(arch_paths=arch_path_map, filename_suffix='.links.tar.gz', callback_func=wrapper_read_links)
|
||||
handler = EventHandler(arch_paths=arch_path_map,
|
||||
filename_suffix='.links.tar.gz',
|
||||
callback_func=wrapper_read_links)
|
||||
return pyinotify.Notifier(manager, handler)
|
||||
|
||||
|
||||
|
@@ -539,7 +539,9 @@ def parse_info(pkgname, filename, iofile):
|
||||
elif blockname:
|
||||
store[blockname].append(line)
|
||||
else:
|
||||
raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname, filename, line))
|
||||
raise Exception("%s: Read package info outside a block while reading from %s: %s" % (pkgname,
|
||||
filename,
|
||||
line))
|
||||
return store
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zoneinfo
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
|
@@ -202,7 +202,8 @@ def non_existing_dependencies(packages):
|
||||
|
||||
|
||||
def non_reproducible_packages(packages):
|
||||
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname'))
|
||||
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD,
|
||||
pkg__pkgname__in=packages.values('pkgname'))
|
||||
return linkify_non_reproducible_packages(statuses)
|
||||
|
||||
|
||||
@@ -227,7 +228,7 @@ def orphan_dependencies(packages):
|
||||
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
|
||||
JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
|
||||
ORDER BY child.pkgname;
|
||||
"""
|
||||
""" # noqa: E501
|
||||
cursor.execute(query)
|
||||
|
||||
for row in cursor.fetchall():
|
||||
|
@@ -216,7 +216,9 @@ def tier0_mirror_auth(request):
|
||||
token = credentials[1]
|
||||
|
||||
groups = Group.objects.filter(name__in=SELECTED_GROUPS)
|
||||
user = User.objects.filter(username=username, is_active=True, groups__in=groups).select_related('userprofile').first()
|
||||
user = User.objects.filter(username=username,
|
||||
is_active=True,
|
||||
groups__in=groups).select_related('userprofile').first()
|
||||
if not user:
|
||||
return unauthorized
|
||||
|
||||
|
@@ -280,7 +280,8 @@ class Package(models.Model):
|
||||
dep_pkgs = list(dep_pkgs)
|
||||
dep = dep_pkgs[0]
|
||||
if len(dep_pkgs) > 1:
|
||||
dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
|
||||
dep_pkgs = [d for d in dep_pkgs
|
||||
if d.pkg.repo.testing == self.repo.testing and d.pkg.repo.staging == self.repo.staging]
|
||||
if len(dep_pkgs) > 0:
|
||||
dep = dep_pkgs[0]
|
||||
trimmed.append(dep)
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import cssmin
|
||||
import jsmin
|
||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
import cssmin
|
||||
|
||||
|
||||
class MinifiedStaticFilesStorage(ManifestStaticFilesStorage):
|
||||
"""
|
||||
|
@@ -35,7 +35,7 @@ def pgp_dev_key_link(key_id):
|
||||
key_id = pad_key_id(key_id)
|
||||
if not key_id:
|
||||
return "Unknown"
|
||||
link_text = (''.join((f'<span>{key_id[i:i+4]}</span>' for i in range(0, len(key_id), 4))))
|
||||
link_text = (''.join((f'<span>{key_id[i:i + 4]}</span>' for i in range(0, len(key_id), 4))))
|
||||
link_text = f'<div class="pgp-key-ids">{link_text}</div>'
|
||||
return pgp_key_link(key_id, link_text)
|
||||
|
||||
@@ -51,7 +51,9 @@ def pgp_key_link(key_id, link_text=None):
|
||||
return format_key(key_id)
|
||||
pgp_server_secure = getattr(settings, 'PGP_SERVER_SECURE', False)
|
||||
scheme = 'https' if pgp_server_secure else 'http'
|
||||
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme, pgp_server, key_id)
|
||||
url = '%s://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % (scheme,
|
||||
pgp_server,
|
||||
key_id)
|
||||
if link_text is None:
|
||||
link_text = '0x%s' % key_id[-8:]
|
||||
values = (url, format_key(key_id), link_text)
|
||||
|
@@ -54,10 +54,12 @@ class NewsCreateView(CreateView):
|
||||
if settings.MAILMAN_PASSWORD:
|
||||
headers['Approved'] = settings.MAILMAN_PASSWORD
|
||||
template = loader.get_template('news/news_email_notification.txt')
|
||||
author = newsitem.author.get_full_name()
|
||||
from_ = f'"Arch Linux: Recent news updates: {author}" <{settings.ANNOUNCE_EMAIL}>'
|
||||
EmailMessage(
|
||||
subject=f'[arch-announce] {newsitem.title}',
|
||||
body=template.render(ctx),
|
||||
from_email=f'"Arch Linux: Recent news updates: {newsitem.author.get_full_name()}" <{settings.ANNOUNCE_EMAIL}>',
|
||||
from_email=from_,
|
||||
to=[settings.ANNOUNCE_EMAIL],
|
||||
headers=headers).send()
|
||||
return super(NewsCreateView, self).form_valid(form)
|
||||
|
@@ -53,7 +53,8 @@ def create_specification(package, log, finder):
|
||||
|
||||
|
||||
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)
|
||||
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]
|
||||
logger.info("Keeping %s signoffs", len(id_signoffs))
|
||||
# FakeSignoffSpecification's have no id
|
||||
id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification, FakeSignoffSpecification)]
|
||||
id_signoffspecs = [g.specification.id for g in groups if not isinstance(g.specification,
|
||||
FakeSignoffSpecification)]
|
||||
logger.info("Keeping %s signoffspecifications", len(id_signoffspecs))
|
||||
|
||||
Signoff.objects.exclude(id__in=id_signoffs).delete()
|
||||
|
@@ -253,7 +253,8 @@ class UpdateManager(models.Manager):
|
||||
if new_pkg:
|
||||
update.action_flag = CHANGE
|
||||
# ensure we should even be logging this
|
||||
if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel and old_pkg.epoch == new_pkg.epoch:
|
||||
if old_pkg.pkgver == new_pkg.pkgver and old_pkg.pkgrel == new_pkg.pkgrel \
|
||||
and old_pkg.epoch == new_pkg.epoch:
|
||||
# all relevant fields were the same; e.g. a force update
|
||||
return
|
||||
else:
|
||||
@@ -395,7 +396,8 @@ class RelatedToBase(models.Model):
|
||||
# actually satisfy the requirements
|
||||
if self.comparison and self.version:
|
||||
alpm = AlpmAPI()
|
||||
pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version, self.comparison, self.version)]
|
||||
pkgs = [pkg for pkg in pkgs if not alpm.available or alpm.compare_versions(pkg.full_version,
|
||||
self.comparison, self.version)]
|
||||
if len(pkgs) == 0:
|
||||
# couldn't find a package in the DB
|
||||
# it should be a virtual depend (or a removed package)
|
||||
|
@@ -67,6 +67,7 @@ def test_sort(client, package):
|
||||
def test_packages(client, package):
|
||||
response = client.get('/opensearch/packages/')
|
||||
assert response.status_code == 200
|
||||
assert 'template="example.com/opensearch/packages/"' in response.content.decode()
|
||||
|
||||
|
||||
def test_packages_suggest(client, package):
|
||||
|
@@ -5,6 +5,7 @@ from collections import defaultdict
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
@@ -21,10 +22,10 @@ from ..utils import get_wrong_permissions, multilib_differences
|
||||
@require_safe
|
||||
@cache_control(public=True, max_age=86400)
|
||||
def opensearch(request):
|
||||
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST'))
|
||||
current_site = Site.objects.get_current()
|
||||
|
||||
return render(request, 'packages/opensearch.xml',
|
||||
{'domain': domain},
|
||||
{'domain': current_site.domain},
|
||||
content_type='application/opensearchdescription+xml')
|
||||
|
||||
|
||||
@@ -140,8 +141,14 @@ def sonames(request):
|
||||
name = request.GET.get('name')
|
||||
|
||||
if name:
|
||||
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname', 'pkg__pkgver', 'pkg__pkgrel', 'pkg__epoch', 'pkg__repo__name')
|
||||
packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'], 'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'], 'repo': soname['pkg__repo__name'].lower()} for soname in sonames]
|
||||
sonames = Soname.objects.filter(name__startswith=name).values('pkg__pkgname',
|
||||
'pkg__pkgver',
|
||||
'pkg__pkgrel',
|
||||
'pkg__epoch',
|
||||
'pkg__repo__name')
|
||||
packages = [{'pkgname': soname['pkg__pkgname'], 'pkgrel': soname['pkg__pkgrel'],
|
||||
'pkgver': soname['pkg__pkgver'], 'epoch': soname['pkg__epoch'],
|
||||
'repo': soname['pkg__repo__name'].lower()} for soname in sonames]
|
||||
else:
|
||||
return HttpResponseBadRequest('name parameter is required')
|
||||
|
||||
|
@@ -38,7 +38,8 @@ class FlagForm(forms.Form):
|
||||
# make sure the message isn't garbage (only punctuation or whitespace)
|
||||
# or spam (using a simple denylist)
|
||||
# and ensure a certain minimum length
|
||||
if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) or len(data) < 3:
|
||||
if re.match(r'^[^0-9A-Za-z]+$', data) or any(fd.keyword in data for fd in FlagDenylist.objects.all()) \
|
||||
or len(data) < 3:
|
||||
raise forms.ValidationError("Enter a valid and useful out-of-date message.")
|
||||
return data
|
||||
|
||||
|
@@ -50,7 +50,8 @@ class Migration(migrations.Migration):
|
||||
('author', models.CharField(max_length=255)),
|
||||
('publishdate', models.DateTimeField(db_index=True, verbose_name='publish date')),
|
||||
('url', models.CharField(max_length=255, verbose_name='URL')),
|
||||
('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='feed', to='planet.Feed')),
|
||||
('feed', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='feed', to='planet.Feed')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Feed Items',
|
||||
|
@@ -14,6 +14,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='feeditem',
|
||||
name='feed',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='planet.feed'),
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='items', to='planet.feed'),
|
||||
),
|
||||
]
|
||||
|
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from operator import attrgetter
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db.models import Count, Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
@@ -25,12 +26,12 @@ def index(request):
|
||||
else:
|
||||
def updates():
|
||||
return get_recent_updates()
|
||||
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST'))
|
||||
current_site = Site.objects.get_current()
|
||||
context = {
|
||||
'news_updates': News.objects.order_by('-postdate', '-id')[:15],
|
||||
'pkg_updates': updates,
|
||||
'staff_groups': StaffGroup.objects.all(),
|
||||
'domain': domain,
|
||||
'domain': current_site.domain,
|
||||
}
|
||||
return render(request, 'public/index.html', context)
|
||||
|
||||
@@ -92,8 +93,9 @@ def feeds(request):
|
||||
@cache_control(max_age=307)
|
||||
def keys(request):
|
||||
profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id')
|
||||
users = User.objects.filter(
|
||||
is_active=True, userprofile__id__in=profile_ids).order_by('first_name', 'last_name').select_related('userprofile')
|
||||
users = User.objects.filter(is_active=True,
|
||||
userprofile__id__in=profile_ids).order_by('first_name',
|
||||
'last_name').select_related('userprofile')
|
||||
user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users
|
||||
if user.userprofile.pgp_key)
|
||||
|
||||
|
@@ -9,8 +9,10 @@ from .views import ReleaseDetailView, ReleaseListView
|
||||
releases_patterns = [
|
||||
path('', ReleaseListView.as_view(), name='releng-release-list'),
|
||||
path('json/', views.releases_json, name='releng-release-list-json'),
|
||||
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()), name='releng-release-detail'),
|
||||
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent), name='releng-release-torrent'),
|
||||
re_path(r'^(?P<version>[-.\w]+)/$', cache_page(311)(ReleaseDetailView.as_view()),
|
||||
name='releng-release-detail'),
|
||||
re_path(r'^(?P<version>[-.\w]+)/torrent/$', cache_page(311)(views.release_torrent),
|
||||
name='releng-release-torrent'),
|
||||
]
|
||||
|
||||
netboot_patterns = [
|
||||
|
@@ -1,5 +1,5 @@
|
||||
-e git+https://github.com/fredj/cssmin.git@master#egg=cssmin
|
||||
Django==5.0.10
|
||||
Django==5.0.11
|
||||
IPy==1.1
|
||||
Markdown==3.3.7
|
||||
bencode.py==4.0.0
|
||||
|
@@ -24,7 +24,6 @@ select = [
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501", # line lengt violation
|
||||
"E731", # Do not assign a `lambda` expression, use a `def`
|
||||
"B904", # Within an `except` clause, raise exceptions with `raise ... from err`
|
||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
@@ -34,3 +33,8 @@ ignore = [
|
||||
"DJ008", # Model does not define `__str__` method
|
||||
"DJ012", # Order of model's inner classes, methods, and fields does not follow the Django Style Guide: `Meta` class should come before `get_absolute_url`
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"*/migrations/*.py", # Ignore Django migrations
|
||||
"src/cssmin/*" # cssmin, not our code
|
||||
]
|
||||
|
@@ -266,7 +266,9 @@ if DEBUG_TOOLBAR:
|
||||
INSTALLED_APPS = [*list(INSTALLED_APPS), 'debug_toolbar']
|
||||
|
||||
if PROMETHEUS_METRICS:
|
||||
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware', *list(MIDDLEWARE), 'django_prometheus.middleware.PrometheusAfterMiddleware']
|
||||
MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware',
|
||||
*list(MIDDLEWARE),
|
||||
'django_prometheus.middleware.PrometheusAfterMiddleware']
|
||||
|
||||
INSTALLED_APPS = [*list(INSTALLED_APPS), 'django_prometheus']
|
||||
|
||||
|
@@ -84,6 +84,8 @@
|
||||
|
||||
<h3>Past donors</h3>
|
||||
|
||||
<p><a href="http://www.dotcom-monitor.com/" title="Dotcom-Monitor">Dotcom-Monitor</a></p>
|
||||
|
||||
<div id="donor-list">
|
||||
<ul>
|
||||
{% for donor in donors %}
|
||||
|
@@ -28,7 +28,9 @@ class Migration(migrations.Migration):
|
||||
('created', models.DateTimeField(db_index=True)),
|
||||
('last_modified', models.DateTimeField(editable=False)),
|
||||
('raw', models.TextField(blank=True)),
|
||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_todolists', to=settings.AUTH_USER_MODEL)),
|
||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='created_todolists',
|
||||
to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'created',
|
||||
@@ -43,13 +45,16 @@ class Migration(migrations.Migration):
|
||||
('created', models.DateTimeField(editable=False)),
|
||||
('last_modified', models.DateTimeField(editable=False)),
|
||||
('removed', models.DateTimeField(blank=True, null=True)),
|
||||
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')], default=0)),
|
||||
('status', models.SmallIntegerField(choices=[(0, 'Incomplete'), (1, 'Complete'), (2, 'In-progress')],
|
||||
default=0)),
|
||||
('comments', models.TextField(blank=True, null=True)),
|
||||
('arch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Arch')),
|
||||
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.Package')),
|
||||
('pkg', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||
to='main.Package')),
|
||||
('repo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Repo')),
|
||||
('todolist', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todolists.Todolist')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'created',
|
||||
|
@@ -13,6 +13,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='todolist',
|
||||
name='kind',
|
||||
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0, help_text='(Rebuild for soname bumps, Task for independent tasks)'),
|
||||
field=models.SmallIntegerField(choices=[(0, 'Rebuild'), (1, 'Task')], default=0,
|
||||
help_text='(Rebuild for soname bumps, Task for independent tasks)'),
|
||||
),
|
||||
]
|
||||
|
@@ -23,7 +23,8 @@ class Todolist(models.Model):
|
||||
description = models.TextField()
|
||||
creator = models.ForeignKey(User, on_delete=models.PROTECT, related_name="created_todolists")
|
||||
created = models.DateTimeField(db_index=True)
|
||||
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES, help_text='(Rebuild for soname bumps, Task for independent tasks)')
|
||||
kind = models.SmallIntegerField(default=REBUILD, choices=KIND_CHOICES,
|
||||
help_text='(Rebuild for soname bumps, Task for independent tasks)')
|
||||
last_modified = models.DateTimeField(editable=False)
|
||||
raw = models.TextField(blank=True)
|
||||
|
||||
|
Reference in New Issue
Block a user