Dispatcharr/apps/channels/managers.py
None 6fed444e43 feat: Auto-sync overhaul (FR #1196)
Per-field channel overrides for auto-synced channels, hide-from-output flag, range-bounded auto-numbering with re-pack, multi-stream channel safety, multi-provider shared-range merging, and an across-the-board move from per-row sync writes to bulk operations.

Migrations:

apps/channels:

0036_channeloverride_and_user_hidden,
0037_backfill_auto_created_by_null,
0038_channelgroupm3uaccount_auto_sync_channel_end,
0039_channel_channel_number_nullable

apps/m3u:
0020_m3uaccount_auto_cleanup_unused_channels

See CHANGELOG.md for the full commit log
2026-05-01 14:05:06 -05:00

55 lines
1.9 KiB
Python

"""
Queryset helpers that resolve effective Channel field values.
Each Channel can optionally have a related ChannelOverride row carrying user
edits to any subset of its user-facing fields. Sync never touches the override
row; provider metadata flows directly into Channel.* and the override table
sits alongside with a nullable value per field. The helpers here coalesce the
two sources into `effective_*` annotations so output querysets can sort,
filter, and emit values correctly at SQL level (avoiding 23+ Python-side
resolutions across the codebase).
"""
from django.db.models.functions import Coalesce
OVERRIDABLE_FIELDS = (
"name",
"channel_number",
"channel_group_id",
"logo_id",
"tvg_id",
"tvc_guide_stationid",
"epg_data_id",
"stream_profile_id",
)
def with_effective_values(queryset, select_related_fks=False):
"""
Annotate the channels queryset with `effective_*` columns that resolve to
the override value when set, otherwise fall back to the channel's own
value. Always eagerly loads the override one-to-one to avoid N+1 when the
caller reads annotated attributes and then the related override.
Pass `select_related_fks=True` when the output path will access FK objects
through the `effective_*_obj` Channel properties; this pulls the override's
logo, channel_group, epg_data, and stream_profile in the same query so
those accessors do not trigger per-row lookups.
"""
annotations = {
f"effective_{field}": Coalesce(
f"override__{field}",
field,
)
for field in OVERRIDABLE_FIELDS
}
qs = queryset.select_related("override").annotate(**annotations)
if select_related_fks:
qs = qs.select_related(
"override__logo",
"override__channel_group",
"override__epg_data",
"override__stream_profile",
)
return qs