Improve performance oprimizing queries (#56)

* Extend test coverage

* Implement query optimization

* More aggressing code reuse

* Add more indexes and optimize usage

* Fix tests

* Further optimizations, improve counting to rely on backend DB

* chore: add Makefile for frontend asset minification

- Add comprehensive Makefile with targets for JS and CSS minification
- Implements instructions from ram/portal/static/js/src/README.md
- Provides targets: install, minify, minify-js, minify-css, clean, watch
- Fix main.min.js to only include theme_selector.js and tabs_selector.js
- Remove validators.js from minified output per README instructions

* Add a Makefile to compile JS and CSS

* docs: add blank line whitespace rule to AGENTS.md

Specify that blank lines must not contain any whitespace (spaces or tabs) to maintain code cleanliness and PEP 8 compliance

* Update for 0.20 release with optimizations

* Improve Makefile
This commit is contained in:
2026-01-25 15:15:51 +01:00
committed by GitHub
parent b9e55936e1
commit bea1c653f0
24 changed files with 2406 additions and 59 deletions

View File

@@ -59,6 +59,11 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
search_fields = ("identifier",) + list_filter
save_as = True
def get_queryset(self, request):
"""Optimize queryset with select_related and prefetch_related."""
qs = super().get_queryset(request)
return qs.with_related()
@admin.display(description="Country")
def country_flag(self, obj):
return format_html(
@@ -117,12 +122,27 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
"Item ID",
]
data = []
# Prefetch related data to avoid N+1 queries
queryset = queryset.select_related(
'company', 'scale'
).prefetch_related(
'tags',
'consist_item__rolling_stock__rolling_class__type'
)
for obj in queryset:
# Cache the type count to avoid recalculating for each item
types = " + ".join(
"{}x {}".format(t["count"], t["type"])
for t in obj.get_type_count()
)
# Cache tags to avoid repeated queries
tags_str = settings.CSV_SEPARATOR_ALT.join(
t.name for t in obj.tags.all()
)
for item in obj.consist_item.all():
types = " + ".join(
"{}x {}".format(t["count"], t["type"])
for t in obj.get_type_count()
)
data.append(
[
obj.uuid,
@@ -134,9 +154,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
obj.scale.scale,
obj.era,
html.unescape(strip_tags(obj.description)),
settings.CSV_SEPARATOR_ALT.join(
t.name for t in obj.tags.all()
),
tags_str,
obj.length,
types,
item.rolling_stock.__str__(),

View File

@@ -0,0 +1,50 @@
# Generated by Django 6.0.1 on 2026-01-18 13:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("consist", "0019_consistitem_load"),
(
"metadata",
"0027_company_company_slug_idx_company_company_country_idx_and_more",
),
("roster", "0041_rollingclass_roster_rc_company_idx_and_more"),
]
operations = [
migrations.AddIndex(
model_name="consist",
index=models.Index(fields=["published"], name="consist_published_idx"),
),
migrations.AddIndex(
model_name="consist",
index=models.Index(fields=["scale"], name="consist_scale_idx"),
),
migrations.AddIndex(
model_name="consist",
index=models.Index(fields=["company"], name="consist_company_idx"),
),
migrations.AddIndex(
model_name="consist",
index=models.Index(
fields=["published", "scale"], name="consist_pub_scale_idx"
),
),
migrations.AddIndex(
model_name="consistitem",
index=models.Index(fields=["load"], name="consist_item_load_idx"),
),
migrations.AddIndex(
model_name="consistitem",
index=models.Index(fields=["order"], name="consist_item_order_idx"),
),
migrations.AddIndex(
model_name="consistitem",
index=models.Index(
fields=["consist", "load"], name="consist_item_con_load_idx"
),
),
]

View File

@@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
from ram.models import BaseModel
from ram.utils import DeduplicatedStorage
from ram.managers import ConsistManager
from metadata.models import Company, Scale, Tag
from roster.models import RollingStock
@@ -35,6 +36,8 @@ class Consist(BaseModel):
blank=True,
)
objects = ConsistManager()
def __str__(self):
return "{0} {1}".format(self.company, self.identifier)
@@ -45,6 +48,11 @@ class Consist(BaseModel):
def length(self):
return self.consist_item.filter(load=False).count()
@property
def loads_count(self):
"""Count of loads in this consist using database aggregation."""
return self.consist_item.filter(load=True).count()
def get_type_count(self):
return self.consist_item.filter(load=False).annotate(
type=models.F("rolling_stock__rolling_class__type__type")
@@ -71,6 +79,18 @@ class Consist(BaseModel):
class Meta:
ordering = ["company", "-creation_time"]
indexes = [
# Index for published filtering
models.Index(fields=["published"], name="consist_published_idx"),
# Index for scale filtering
models.Index(fields=["scale"], name="consist_scale_idx"),
# Index for company filtering
models.Index(fields=["company"], name="consist_company_idx"),
# Composite index for published+scale filtering
models.Index(
fields=["published", "scale"], name="consist_pub_scale_idx"
),
]
class ConsistItem(models.Model):
@@ -86,9 +106,19 @@ class ConsistItem(models.Model):
constraints = [
models.UniqueConstraint(
fields=["consist", "rolling_stock"],
name="one_stock_per_consist"
name="one_stock_per_consist",
)
]
indexes = [
# Index for filtering by load status
models.Index(fields=["load"], name="consist_item_load_idx"),
# Index for ordering
models.Index(fields=["order"], name="consist_item_order_idx"),
# Composite index for consist+load filtering
models.Index(
fields=["consist", "load"], name="consist_item_con_load_idx"
),
]
def __str__(self):
return "{0}".format(self.rolling_stock)