mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 17:16:26 +00:00
Update expiration date handling in M3U components to ensure UTC format
This commit is contained in:
parent
1d4603dadd
commit
09d5f6bb9f
2 changed files with 30 additions and 15 deletions
|
|
@ -7,6 +7,7 @@ from apps.channels.models import ChannelGroup, ChannelGroupM3UAccount
|
|||
from apps.channels.serializers import (
|
||||
ChannelGroupM3UAccountSerializer,
|
||||
)
|
||||
from datetime import timezone as dt_tz
|
||||
import logging
|
||||
import json
|
||||
|
||||
|
|
@ -214,10 +215,14 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
|
||||
# Surface default profile's exp_date for the form.
|
||||
# Use prefetch cache (obj.profiles.all()) to avoid an extra query per account.
|
||||
# Always emit a Z-suffix UTC string so JS new Date() never misinterprets it as local.
|
||||
default_profile = next((p for p in instance.profiles.all() if p.is_default), None)
|
||||
data["exp_date"] = (
|
||||
default_profile.exp_date.isoformat() if default_profile and default_profile.exp_date else None
|
||||
)
|
||||
exp = default_profile.exp_date if default_profile else None
|
||||
if exp:
|
||||
exp_utc = exp.astimezone(dt_tz.utc) if exp.tzinfo else exp.replace(tzinfo=dt_tz.utc)
|
||||
data["exp_date"] = exp_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
else:
|
||||
data["exp_date"] = None
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -286,12 +291,19 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
memberships_to_update, ["enabled"]
|
||||
)
|
||||
|
||||
# Write exp_date through to the default profile
|
||||
# Write exp_date through to the default profile.
|
||||
# Use a fresh DB query (not the prefetch cache) so we get the profile
|
||||
# object AFTER the post_save signal (create_profile_for_m3u_account)
|
||||
# has already updated max_streams, avoiding a stale-value overwrite.
|
||||
if exp_date != "__NOT_SET__":
|
||||
default_profile = instance.profiles.filter(is_default=True).first()
|
||||
if default_profile:
|
||||
default_profile.exp_date = exp_date
|
||||
default_profile.save()
|
||||
default_profile.save(update_fields=['exp_date'])
|
||||
# Invalidate the profiles prefetch cache so to_representation
|
||||
# sees the updated exp_date rather than the pre-request snapshot.
|
||||
if '_prefetched_objects_cache' in instance.__dict__:
|
||||
instance._prefetched_objects_cache.pop('profiles', None)
|
||||
|
||||
return instance
|
||||
|
||||
|
|
@ -342,7 +354,9 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
expiring = [p.exp_date for p in obj.profiles.all() if p.is_active and p.exp_date]
|
||||
if not expiring:
|
||||
return None
|
||||
return min(expiring).isoformat()
|
||||
exp = min(expiring)
|
||||
exp_utc = exp.astimezone(dt_tz.utc) if exp.tzinfo else exp.replace(tzinfo=dt_tz.utc)
|
||||
return exp_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
def get_all_expirations(self, obj):
|
||||
"""Return exp_date info for every profile that has one (for tooltip)."""
|
||||
|
|
@ -355,7 +369,7 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
{
|
||||
"profile_id": p.id,
|
||||
"profile_name": p.name,
|
||||
"exp_date": p.exp_date.isoformat(),
|
||||
"exp_date": (p.exp_date.astimezone(dt_tz.utc) if p.exp_date.tzinfo else p.exp_date.replace(tzinfo=dt_tz.utc)).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
"is_active": p.is_active,
|
||||
}
|
||||
for p in profiles
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const M3U = ({
|
|||
|
||||
const [playlist, setPlaylist] = useState(null);
|
||||
const [file, setFile] = useState(null);
|
||||
const [expDate, setExpDate] = useState(null);
|
||||
const [profileModalOpen, setProfileModalOpen] = useState(false);
|
||||
const [groupFilterModalOpen, setGroupFilterModalOpen] = useState(false);
|
||||
const [filterModalOpen, setFilterModalOpen] = useState(false);
|
||||
|
|
@ -70,7 +71,6 @@ const M3U = ({
|
|||
stale_stream_days: 7,
|
||||
priority: 0,
|
||||
enable_vod: false,
|
||||
exp_date: null,
|
||||
},
|
||||
|
||||
validate: {
|
||||
|
|
@ -103,8 +103,8 @@ const M3U = ({
|
|||
? m3uAccount.priority
|
||||
: 0,
|
||||
enable_vod: m3uAccount.enable_vod || false,
|
||||
exp_date: m3uAccount.exp_date ? new Date(m3uAccount.exp_date) : null,
|
||||
});
|
||||
setExpDate(m3uAccount.exp_date ? new Date(m3uAccount.exp_date) : null);
|
||||
|
||||
// Determine schedule type from existing data
|
||||
setScheduleType(
|
||||
|
|
@ -122,6 +122,7 @@ const M3U = ({
|
|||
setPlaylist(null);
|
||||
form.reset();
|
||||
setScheduleType('interval');
|
||||
setExpDate(null);
|
||||
}
|
||||
}, [m3uAccount]);
|
||||
|
||||
|
|
@ -134,13 +135,13 @@ const M3U = ({
|
|||
const onSubmit = async () => {
|
||||
const { create_epg, ...values } = form.getValues();
|
||||
|
||||
// Convert exp_date Date object to ISO string for the API
|
||||
// Convert exp_date (from controlled state) to ISO string for the API
|
||||
if (values.account_type === 'XC') {
|
||||
// XC accounts have exp_date auto-managed server-side; don't send it
|
||||
delete values.exp_date;
|
||||
} else if (values.exp_date instanceof Date) {
|
||||
values.exp_date = values.exp_date.toISOString();
|
||||
} else if (!values.exp_date) {
|
||||
} else if (expDate instanceof Date) {
|
||||
values.exp_date = expDate.toISOString();
|
||||
} else {
|
||||
values.exp_date = null;
|
||||
}
|
||||
|
||||
|
|
@ -387,8 +388,8 @@ const M3U = ({
|
|||
placeholder="No expiration"
|
||||
clearable
|
||||
valueFormat="MMM D, YYYY h:mm A"
|
||||
{...form.getInputProps('exp_date')}
|
||||
key={form.key('exp_date')}
|
||||
value={expDate}
|
||||
onChange={(v) => setExpDate(v ? new Date(v) : null)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue