Bug Fix: EPG channel name vlaue too long for type character varying(255) (Fixes #1134)
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run

This commit is contained in:
SergeantPanda 2026-04-21 09:23:35 -05:00
parent 844ef4667a
commit 69e0cba86e
7 changed files with 60 additions and 3 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **EPG channel name truncation**: EPG sources that include long `<display-name>` values (e.g. event-based channels with descriptions appended to the name) would crash the channel-parse task with a `value too long for type character varying(255)` PostgreSQL error and silently discard the entire batch. The `EPGData.name`, `Stream.name`, and `Channel.name` fields have been widened to 512 characters, and names exceeding this limit are now truncated with a warning log rather than aborting the import. (Fixes #1134)
### Added
- **Plugin warning & disclaimer components**: extracted shared plugin warning UI into a new `PluginWarnings.jsx` component and normalized warning/disclaimer usage across all plugin action modals. New reusable components: `PluginSecurityWarning` (untrusted code), `PluginSupportDisclaimer` (community support scope), `PluginDowngradeWarning` (version downgrade), `PluginInfoNote` (informational), and `PluginRestartWarning` (backend restart on import). Updated `Plugins.jsx`, `AvailablePluginCard.jsx`, and `PluginCard.jsx` to use the shared components. — Thanks [@sethwv](https://github.com/sethwv)

View file

@ -0,0 +1,23 @@
# Generated by Django 6.0.4 on 2026-04-21 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dispatcharr_channels', '0034_remove_stream_dispatcharr_stream_id_idx_and_more'),
]
operations = [
migrations.AlterField(
model_name='channel',
name='name',
field=models.CharField(max_length=512),
),
migrations.AlterField(
model_name='stream',
name='name',
field=models.CharField(default='Default Stream', max_length=512),
),
]

View file

@ -56,7 +56,7 @@ class Stream(models.Model):
Represents a single stream (e.g. from an M3U source or custom URL).
"""
name = models.CharField(max_length=255, default="Default Stream")
name = models.CharField(max_length=512, default="Default Stream")
url = models.URLField(max_length=4096, blank=True, null=True)
m3u_account = models.ForeignKey(
M3UAccount,
@ -294,7 +294,7 @@ class ChannelManager(models.Manager):
class Channel(models.Model):
channel_number = models.FloatField(db_index=True)
name = models.CharField(max_length=255)
name = models.CharField(max_length=512)
logo = models.ForeignKey(
"Logo",
on_delete=models.SET_NULL,

View file

@ -0,0 +1,18 @@
# Generated by Django 6.0.4 on 2026-04-21 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0021_epgsource_priority'),
]
operations = [
migrations.AlterField(
model_name='epgdata',
name='name',
field=models.CharField(max_length=512),
),
]

View file

@ -137,7 +137,7 @@ class EPGData(models.Model):
# Removed the Channel foreign key. We now just store the original tvg_id
# and a name (which might simply be the tvg_id if no real channel exists).
tvg_id = models.CharField(max_length=255, null=True, blank=True, db_index=True)
name = models.CharField(max_length=255)
name = models.CharField(max_length=512)
icon_url = models.URLField(max_length=500, null=True, blank=True)
epg_source = models.ForeignKey(
EPGSource,

View file

@ -966,6 +966,7 @@ def parse_channels_only(source):
batch_size = 500 # Process in batches to limit memory usage
progress = 0 # Initialize progress variable here
icon_url_max_length = EPGData._meta.get_field('icon_url').max_length # Get max length for icon_url field
name_max_length = EPGData._meta.get_field('name').max_length # Get max length for name field
# Track memory at key points
if process:
@ -1022,6 +1023,10 @@ def parse_channels_only(source):
if not display_name:
display_name = tvg_id
if display_name and len(display_name) > name_max_length:
logger.warning(f"EPG display name too long ({len(display_name)} > {name_max_length}), truncating: {display_name[:80]}...")
display_name = display_name[:name_max_length]
# Use lazy loading approach to reduce memory usage
if tvg_id in existing_tvg_ids:
# Only fetch the object if we need to update it and it hasn't been loaded yet

View file

@ -1087,6 +1087,8 @@ def process_m3u_batch_direct(account_id, batch, groups, hash_keys):
streams_to_update = []
stream_hashes = {}
name_max_length = Stream._meta.get_field('name').max_length
logger.debug(f"Processing batch of {len(batch)} for M3U account {account_id}")
if compiled_filters:
logger.debug(f"Using compiled filters: {[f[1].regex_pattern for f in compiled_filters]}")
@ -1099,6 +1101,11 @@ def process_m3u_batch_direct(account_id, batch, groups, hash_keys):
logger.warning(f"Skipping stream '{name}': URL too long ({len(url)} characters, max 4096)")
continue
# Truncate name if it exceeds the model field limit
if name and len(name) > name_max_length:
logger.warning(f"Stream name too long ({len(name)} > {name_max_length}), truncating: {name[:80]}...")
name = name[:name_max_length]
tvg_id, tvg_logo = get_case_insensitive_attr(
stream_info["attributes"], "tvg-id", ""
), get_case_insensitive_attr(stream_info["attributes"], "tvg-logo", "")