Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion hosts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,12 @@ class HostAdmin(admin.ModelAdmin):
readonly_fields = ('packages', 'updates')


class HostRepoAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request) \
.select_related('host', 'repo')
return qs


admin.site.register(Host, HostAdmin)
admin.site.register(HostRepo)
admin.site.register(HostRepo, HostRepoAdmin)
2 changes: 1 addition & 1 deletion hosts/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@

class HostManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('osvariant', 'arch', 'domain')
return super().get_queryset().select_related('osvariant', 'arch', 'domain', 'osvariant__arch')
18 changes: 11 additions & 7 deletions hosts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _get_filtered_hosts(filter_params):
"""Helper to reconstruct filtered queryset from filter params."""
params = parse_qs(filter_params)

hosts = Host.objects.select_related('osvariant', 'arch', 'domain')
hosts = Host.objects.all()

if 'domain_id' in params:
hosts = hosts.filter(domain=params['domain_id'][0])
Expand Down Expand Up @@ -79,7 +79,7 @@ def _get_filtered_hosts(filter_params):
@login_required
def host_list(request):
# Use cached count fields instead of expensive annotations
hosts = Host.objects.select_related('osvariant', 'arch', 'domain')
hosts = Host.objects.all()

if 'domain_id' in request.GET:
hosts = hosts.filter(domain=request.GET['domain_id'])
Expand Down Expand Up @@ -130,7 +130,8 @@ def host_list(request):
filter_list.append(Filter(request, 'Domain', 'domain_id', Domain.objects.all()))
filter_list.append(Filter(request, 'OS Release', 'osrelease_id',
OSRelease.objects.filter(osvariant__host__in=hosts)))
filter_list.append(Filter(request, 'OS Variant', 'osvariant_id', OSVariant.objects.filter(host__in=hosts)))
filter_list.append(Filter(request, 'OS Variant', 'osvariant_id',
OSVariant.objects.filter(host__in=hosts).select_related('arch')))
filter_list.append(Filter(request, 'Architecture', 'arch_id', MachineArchitecture.objects.filter(host__in=hosts)))
filter_list.append(Filter(request, 'Reboot Required', 'reboot_required', {'true': 'Yes', 'false': 'No'}))
filter_bar = FilterBar(request, filter_list)
Expand Down Expand Up @@ -158,12 +159,15 @@ def host_list(request):
def host_detail(request, hostname):
host = get_object_or_404(Host, hostname=hostname)
reports = Report.objects.filter(host=hostname).order_by('-created')[:3]
hostrepos = HostRepo.objects.filter(host=host)
hostrepos = HostRepo.objects.filter(host=host).select_related('repo')

# Build packages list with update info
updates_by_package = {u.oldpackage_id: u for u in host.updates.select_related('oldpackage', 'newpackage')}
updates_by_package = {u.oldpackage_id: u for u in host.updates.select_related('oldpackage',
'newpackage',
'newpackage__name',
'newpackage__arch')}
packages_with_updates = []
for package in host.packages.select_related('name', 'arch').order_by('name__name'):
for package in host.packages.order_by('name__name'):
package.update = updates_by_package.get(package.id)
packages_with_updates.append(package)

Expand Down Expand Up @@ -297,7 +301,7 @@ class HostViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows hosts to be viewed or edited.
"""
queryset = Host.objects.select_related('osvariant', 'arch', 'domain').all()
queryset = Host.objects.all()
serializer_class = HostSerializer
filterset_class = HostFilter

Expand Down
10 changes: 5 additions & 5 deletions operatingsystems/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@
OSRELEASE_NAME_TEMPLATE = '<a href="{{ record.get_absolute_url }}">{{ record.name }}</a>'
OSRELEASE_REPOS_TEMPLATE = (
'<a href="{% url \'repos:repo_list\' %}?osrelease_id={{ record.id }}">'
'{{ record.repos.count }}</a>'
'{{ record.repos_count }}</a>'
)
OSVARIANTS_TEMPLATE = (
'<a href="{% url \'operatingsystems:osvariant_list\' %}?osrelease_id={{ record.id }}">'
'{{ record.osvariant_set.count }}</a>'
'{{ record.osvariant_count }}</a>'
)
OSRELEASE_HOSTS_TEMPLATE = (
'{% load common %}'
'<a href="{% url \'hosts:host_list\' %}?osrelease_id={{ record.id }}">{% host_count record %}</a>'
)
OSRELEASE_ERRATA_TEMPLATE = (
'<a href="{% url \'errata:erratum_list\' %}?osrelease_id={{ record.id }}">'
'{{ record.erratum_set.count }}</a>'
'{{ record.erratum_count }}</a>'
)

# OSVariantTable templates
Expand All @@ -47,15 +47,15 @@
)
OSVARIANT_HOSTS_TEMPLATE = (
'<a href="{% url \'hosts:host_list\' %}?osvariant_id={{ record.id }}">'
'{{ record.host_set.count }}</a>'
'{{ record.hosts_count }}</a>'
)
OSVARIANT_OSRELEASE_TEMPLATE = (
'{% if record.osrelease %}'
'<a href="{{ record.osrelease.get_absolute_url }}">{{ record.osrelease }}</a>'
'{% endif %}'
)
REPOS_OSRELEASE_TEMPLATE = (
'{% if record.osrelease.repos.count != None %}{{ record.osrelease.repos.count }}{% else %}0{% endif %}'
'{% if record.osrelease.repos.count != None %}{{ record.repos_count }}{% else %}0{% endif %}'
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{% if osvariant_count == 0 %}
{{ osrelease }} has no Variants
{% else %}
{% gen_table osrelease.osvariant_set.select_related %}
{% gen_table osrelease.osvariant_set.all %}
{% endif %}
</div>
</div>
Expand All @@ -54,7 +54,7 @@
{% if repos_count == 0 %}
{{ osrelease }} has no Repositories
{% else %}
{% gen_table osrelease.repos.select_related %}
{% gen_table osrelease.repos.all %}
{% endif %}
{% if user.is_authenticated and perms.is_admin %}
<div class="well well-sm">
Expand Down
14 changes: 11 additions & 3 deletions operatingsystems/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Q
from django.db.models import Count, Prefetch, Q
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django_tables2 import RequestConfig
Expand All @@ -34,6 +34,7 @@
OSReleaseSerializer, OSVariantSerializer,
)
from operatingsystems.tables import OSReleaseTable, OSVariantTable
from repos.models import Repository
from util import sanitize_filter_params


Expand Down Expand Up @@ -182,7 +183,11 @@ def delete_nohost_osvariants(request):

@login_required
def osrelease_list(request):
osreleases = OSRelease.objects.all()
osreleases = OSRelease.objects.all().order_by('name').annotate(
repos_count=Count('repos', distinct=True),
osvariant_count=Count('osvariant', distinct=True),
erratum_count=Count('erratum', distinct=True),
)

if 'erratum_id' in request.GET:
osreleases = osreleases.filter(erratum=request.GET['erratum_id'])
Expand Down Expand Up @@ -216,7 +221,10 @@ def osrelease_list(request):

@login_required
def osrelease_detail(request, osrelease_id):
osrelease = get_object_or_404(OSRelease, id=osrelease_id)
repos = Prefetch('repos', Repository.objects.prefetch_related('mirror_set'))
osvariant_set = Prefetch('osvariant_set', OSVariant.objects.prefetch_related('host_set'))
osrelease = get_object_or_404(OSRelease.objects.prefetch_related(repos, osvariant_set),
id=osrelease_id)

if request.method == 'POST':
repos_form = AddReposToOSReleaseForm(request.POST, instance=osrelease)
Expand Down
6 changes: 6 additions & 0 deletions packages/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class PackageAdmin(admin.ModelAdmin):
class PackageUpdateAdmin(admin.ModelAdmin):
readonly_fields = ('oldpackage', 'newpackage')

def get_queryset(self, request):
qs = super().get_queryset(request) \
.select_related('oldpackage__name', 'oldpackage__arch',
'newpackage__name', 'newpackage__arch')
return qs


admin.site.register(Package, PackageAdmin)
admin.site.register(PackageName)
Expand Down
2 changes: 1 addition & 1 deletion packages/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class PackageNameTable(BaseTable):
attrs={'th': {'class': 'col-sm-5'}, 'td': {'class': 'col-sm-5'}},
)
versions = tables.TemplateColumn(
'{{ record.package_set.count }}',
'{{ record.package_count }}',
orderable=False,
verbose_name='Versions',
attrs={'th': {'class': 'col-sm-1'}, 'td': {'class': 'col-sm-1'}},
Expand Down
17 changes: 12 additions & 5 deletions packages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@

@login_required
def package_list(request):
packages = Package.objects.select_related('name', 'arch')
packages = Package.objects.all()

if 'arch_id' in request.GET:
packages = packages.filter(arch=request.GET['arch_id']).distinct()
packages = packages.filter(arch=request.GET['arch_id'])

if 'packagetype' in request.GET:
packages = packages.filter(packagetype=request.GET['packagetype']).distinct()
packages = packages.filter(packagetype=request.GET['packagetype'])

if 'erratum_id' in request.GET:
if request.GET['type'] == 'affected':
Expand Down Expand Up @@ -109,12 +109,14 @@ def package_list(request):
filter_list.append(Filter(request, 'Architecture', 'arch_id', PackageArchitecture.objects.all()))
filter_bar = FilterBar(request, filter_list)

count = packages.count() # faster without joining tables from annotate below
packages = packages.annotate(
host_count=Count('host', distinct=True),
repo_count=Count('mirror__repo', distinct=True),
affected_count=Count('affected_by_erratum', distinct=True),
fixed_count=Count('provides_fix_in_erratum', distinct=True),
)
packages.count = lambda: count

table = PackageTable(packages)
RequestConfig(request, paginate={'per_page': 50}).configure(table)
Expand Down Expand Up @@ -151,7 +153,12 @@ def package_name_list(request):
filter_list.append(Filter(request, 'Architecture', 'arch_id', PackageArchitecture.objects.all()))
filter_bar = FilterBar(request, filter_list)

packages = packages.annotate(host_count=Count('package__host', distinct=True))
count = packages.count() # faster without joining tables from annotate below
packages = packages.annotate(
host_count=Count('package__host', distinct=True),
package_count=Count('package', distinct=True),
)
packages.count = lambda: count

table = PackageNameTable(packages)
RequestConfig(request, paginate={'per_page': 50}).configure(table)
Expand Down Expand Up @@ -245,7 +252,7 @@ class PackageViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows packages to be viewed or edited.
"""
queryset = Package.objects.select_related('name', 'arch').all()
queryset = Package.objects.all()
serializer_class = PackageSerializer
filterset_fields = [
'name',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.29 on 2026-04-20 15:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('reports', '0005_alter_report_options'),
]

operations = [
migrations.AlterModelOptions(
name='report',
options={'ordering': ['-created'], 'verbose_name': 'Report', 'verbose_name_plural': 'Reports'},
),
migrations.AlterField(
model_name='report',
name='created',
field=models.DateTimeField(auto_now_add=True, db_index=True),
),
]
2 changes: 1 addition & 1 deletion reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

class Report(models.Model):

created = models.DateTimeField(auto_now_add=True)
created = models.DateTimeField(auto_now_add=True, db_index=True)
host = models.CharField(max_length=255, null=True)
domain = models.CharField(max_length=255, null=True)
tags = models.CharField(max_length=255, null=True, default='')
Expand Down
2 changes: 1 addition & 1 deletion repos/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
REPO_NAME_TEMPLATE = '<a href="{{ record.get_absolute_url }}">{{ record }}</a>'
MIRRORS_TEMPLATE = (
'<a href="{% url \'repos:mirror_list\' %}?repo_id={{ record.id }}">'
'{{ record.mirror_set.count }}</a>'
'{{ record.mirror_count }}</a>'
)
REPO_ENABLED_TEMPLATE = '{% load common %}{% yes_no_img record.enabled %}'
SECURITY_TEMPLATE = '{% load common %}{% yes_no_img record.security %}'
Expand Down
8 changes: 4 additions & 4 deletions repos/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import IntegrityError
from django.db.models import Q
from django.db.models import Count, Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
Expand All @@ -46,7 +46,7 @@
@login_required
def repo_list(request):

repos = Repository.objects.select_related('arch').order_by('name')
repos = Repository.objects.order_by('name').annotate(mirror_count=Count('mirror', distinct=True))

if 'repotype' in request.GET:
repos = repos.filter(repotype=request.GET['repotype'])
Expand Down Expand Up @@ -417,7 +417,7 @@ def _get_filtered_repos(filter_params):
"""Helper to reconstruct filtered queryset from filter params."""
params = parse_qs(filter_params)

repos = Repository.objects.select_related('arch').order_by('name')
repos = Repository.objects.order_by('name')

if 'repotype' in params:
repos = repos.filter(repotype=params['repotype'][0])
Expand Down Expand Up @@ -588,7 +588,7 @@ class RepositoryViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows repositories to be viewed or edited.
"""
queryset = Repository.objects.select_related('arch').all()
queryset = Repository.objects.all()
serializer_class = RepositorySerializer


Expand Down
2 changes: 1 addition & 1 deletion security/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
CWE_DESCRIPTION_TEMPLATE = '<span class="expandable-text">{{ record.description }}</span>'
CWE_CVES_TEMPLATE = (
'<a href="{% url \'security:cve_list\' %}?cwe_id={{ record.cwe_id }}">'
'{{ record.cve_set.count }}</a>'
'{{ record.cve_count }}</a>'
)

# ReferenceTable templates
Expand Down
14 changes: 8 additions & 6 deletions security/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# along with Patchman. If not, see <http://www.gnu.org/licenses/>

from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, render
from django_tables2 import RequestConfig
from rest_framework import viewsets
Expand All @@ -32,7 +32,7 @@

@login_required
def cwe_list(request):
cwes = CWE.objects.all()
cwes = CWE.objects.all().annotate(cve_count=Count('cve', distinct=True)).order_by('cwe_id')

if 'search' in request.GET:
terms = request.GET['search'].lower()
Expand Down Expand Up @@ -65,7 +65,9 @@ def cwe_detail(request, cwe_id):

@login_required
def cve_list(request):
cves = CVE.objects.all()
cves = CVE.objects.all() \
.prefetch_related('cvss_scores', 'cwes', 'erratum_set') \
.order_by('-cve_id')

if 'erratum_id' in request.GET:
cves = cves.filter(erratum=request.GET['erratum_id'])
Expand Down Expand Up @@ -117,10 +119,10 @@ def cve_detail(request, cve_id):

@login_required
def reference_list(request):
refs = Reference.objects.all().order_by('ref_type')
refs = Reference.objects.all().prefetch_related('erratum_set').order_by('ref_type')

if 'ref_type' in request.GET:
refs = refs.filter(ref_type=request.GET['ref_type']).distinct()
refs = refs.filter(ref_type=request.GET['ref_type'])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to restore distinct() here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty certain distinct() has no effect here in the resultset other than slowing down the query. 'ref_type' is a local field unlike the 'package__' filter in packages/views.py.


if 'erratum_id' in request.GET:
refs = refs.filter(erratum__id=request.GET['erratum_id'])
Expand All @@ -137,7 +139,7 @@ def reference_list(request):

filter_list = []
filter_list.append(Filter(request, 'Reference Type', 'ref_type',
Reference.objects.values_list('ref_type', flat=True).distinct()))
Reference.objects.values_list('ref_type', flat=True)))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I was expecting distinct() here to be useful indeed. Since ref_type would be shared by many References with different urls. I'm not sure where it is deduplicated but doing it without distinct() saves quite a bit of time and the Filter by on the side does not show any duplicates.

With .distinct() it takes 325ms:

SELECT DISTINCT "security_reference"."ref_type",
       "security_reference"."url"
  FROM "security_reference"
 ORDER BY "security_reference"."ref_type" ASC,
          "security_reference"."url" ASC

Without .distinct() it's only 17ms:

SELECT "security_reference"."ref_type"
  FROM "security_reference"
 ORDER BY "security_reference"."ref_type" ASC,
          "security_reference"."url" ASC

Running it in ./manage.py dbshell will reveal it returns many more rows (ie. all of them); it is just computational cheaper it seems. I can also revert this if you want me to

filter_bar = FilterBar(request, filter_list)

table = ReferenceTable(refs)
Expand Down
Loading
Loading