updated timestamp and extension checks for m3 uand epg

This commit is contained in:
dekzter 2025-04-07 12:46:45 -04:00
parent 20c8ff2179
commit e507c6f23c
14 changed files with 106 additions and 53 deletions

View file

@ -0,0 +1,24 @@
# Generated by Django 5.1.6 on 2025-04-07 16:29
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0007_populate_periodic_tasks'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Time when this source was created'),
),
migrations.AddField(
model_name='epgsource',
name='updated_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Time when this source was last updated'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-04-07 16:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0008_epgsource_created_at_epgsource_updated_at'),
]
operations = [
migrations.AlterField(
model_name='epgsource',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Time when this source was created'),
),
migrations.AlterField(
model_name='epgsource',
name='updated_at',
field=models.DateTimeField(auto_now=True, help_text='Time when this source was last updated'),
),
]

View file

@ -17,6 +17,14 @@ class EPGSource(models.Model):
refresh_task = models.ForeignKey(
PeriodicTask, on_delete=models.SET_NULL, null=True, blank=True
)
created_at = models.DateTimeField(
auto_now_add=True,
help_text="Time when this source was created"
)
updated_at = models.DateTimeField(
auto_now=True,
help_text="Time when this source was last updated"
)
def __str__(self):
return self.name

View file

@ -4,10 +4,11 @@ from apps.channels.models import Channel
class EPGSourceSerializer(serializers.ModelSerializer):
epg_data_ids = serializers.SerializerMethodField()
read_only_fields = ['created_at', 'updated_at']
class Meta:
model = EPGSource
fields = ['id', 'name', 'source_type', 'url', 'api_key', 'is_active', 'epg_data_ids', 'refresh_interval']
fields = ['id', 'name', 'source_type', 'url', 'api_key', 'is_active', 'epg_data_ids', 'refresh_interval', 'created_at', 'updated_at']
def get_epg_data_ids(self, obj):
return list(obj.epgs.values_list('id', flat=True))

View file

@ -50,6 +50,8 @@ def refresh_epg_data(source_id):
elif source.source_type == 'schedules_direct':
fetch_schedules_direct(source)
source.save(update_fields=['updated_at'])
release_task_lock('refresh_epg_data', source_id)
def fetch_xmltv(source):

View file

@ -56,7 +56,7 @@ class M3UAccountSerializer(serializers.ModelSerializer):
required=True
)
profiles = M3UAccountProfileSerializer(many=True, read_only=True)
read_only_fields = ['locked']
read_only_fields = ['locked', 'created_at', 'updated_at']
# channel_groups = serializers.SerializerMethodField()
channel_groups = ChannelGroupM3UAccountSerializer(source='channel_group', many=True, required=False)

View file

@ -430,6 +430,7 @@ def refresh_single_m3u_account(account_id):
# Calculate elapsed time
elapsed_time = end_time - start_time
account.save(update_fields=['updated_at'])
print(f"Function took {elapsed_time} seconds to execute.")

View file

@ -48,7 +48,7 @@ ENV PATH="/dispatcharrpy/bin:$PATH" \
# Copy the virtual environment and application from the builder stage
COPY --from=builder /dispatcharrpy /dispatcharrpy
COPY --from=builder /app /app
COPY --from=frontend-builder /app/frontend /app/frontend
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
# Run collectstatic after frontend assets are copied
RUN cd /app && python manage.py collectstatic --noinput

View file

@ -1,40 +1,31 @@
// Modal.js
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import React from 'react';
import API from '../../api';
import { Flex, TextInput, Button, Modal } from '@mantine/core';
import { isNotEmpty, useForm } from '@mantine/form';
const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
const formik = useFormik({
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
name: channelGroup ? channelGroup.name : '',
},
validationSchema: Yup.object({
name: Yup.string().required('Name is required'),
}),
onSubmit: async (values, { setSubmitting, resetForm }) => {
if (channelGroup?.id) {
await API.updateChannelGroup({ id: channelGroup.id, ...values });
} else {
await API.addChannelGroup(values);
}
resetForm();
setSubmitting(false);
onClose();
validate: {
name: isNotEmpty('Specify a name'),
},
});
useEffect(() => {
const onSubmit = async () => {
const values = form.getValues();
if (channelGroup) {
formik.setValues({
name: channelGroup.name,
});
await API.updateChannelGroup({ id: channelGroup.id, ...values });
} else {
formik.resetForm();
await API.addChannelGroup(values);
}
}, [channelGroup]);
return form.reset();
};
if (!isOpen) {
return <></>;
@ -42,14 +33,13 @@ const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
return (
<Modal opened={isOpen} onClose={onClose} title="Channel Group">
<form onSubmit={formik.handleSubmit}>
<form onSubmit={form.onSubmit(onSubmit)}>
<TextInput
id="name"
name="name"
label="Name"
value={formik.values.name}
onChange={formik.handleChange}
error={formik.touched.name}
{...form.getInputProps('name')}
key={form.key('name')}
/>
<Flex mih={50} gap="xs" justify="flex-end" align="flex-end">
@ -57,7 +47,7 @@ const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => {
type="submit"
variant="contained"
color="primary"
disabled={formik.isSubmitting}
disabled={form.submitting}
size="small"
>
Submit

View file

@ -1,22 +1,7 @@
// Modal.js
import React, { useState, useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import React from 'react';
import API from '../../api';
import useEPGsStore from '../../store/epgs';
import {
LoadingOverlay,
TextInput,
Button,
Checkbox,
Modal,
Flex,
NativeSelect,
NumberInput,
Space,
Select,
Alert,
} from '@mantine/core';
import { Button, Modal, Flex, Select, Alert } from '@mantine/core';
import useChannelsStore from '../../store/channels';
import { DateTimePicker } from '@mantine/dates';
import { CircleAlert } from 'lucide-react';
@ -61,6 +46,8 @@ const DVR = ({ recording = null, channel = null, isOpen, onClose }) => {
...values,
channel: channel_id,
});
form.reset();
onClose();
};
@ -110,7 +97,12 @@ const DVR = ({ recording = null, channel = null, isOpen, onClose }) => {
/>
<Flex mih={50} gap="xs" justify="flex-end" align="flex-end">
<Button type="submit" variant="contained" size="small">
<Button
type="submit"
variant="contained"
size="small"
disabled={form.submitting}
>
Submit
</Button>
</Flex>

View file

@ -892,7 +892,7 @@ const ChannelsTable = ({}) => {
<Menu>
<Menu.Target>
<ActionIcon variant="transparent" size="sm">
<CircleEllipsis size="18" />
<EllipsisVertical size="18" />
</ActionIcon>
</Menu.Target>

View file

@ -17,6 +17,7 @@ import {
import { notifications } from '@mantine/notifications';
import { IconSquarePlus } from '@tabler/icons-react';
import { RefreshCcw, SquareMinus, SquarePen } from 'lucide-react';
import dayjs from 'dayjs';
const EPGsTable = () => {
const [epg, setEPG] = useState(null);
@ -44,6 +45,11 @@ const EPGsTable = () => {
accessorKey: 'max_streams',
enableSorting: false,
},
{
header: 'Updated',
accessorFn: (row) => dayjs(row.updated_at).format('MMMM D, YYYY h:mma'),
enableSorting: false,
},
],
[]
);

View file

@ -16,6 +16,7 @@ import {
} from '@mantine/core';
import { SquareMinus, SquarePen, RefreshCcw, Check, X } from 'lucide-react';
import { IconSquarePlus } from '@tabler/icons-react'; // Import custom icons
import dayjs from 'dayjs';
const M3UTable = () => {
const [playlist, setPlaylist] = useState(null);
@ -70,6 +71,11 @@ const M3UTable = () => {
</Box>
),
},
{
header: 'Updated',
accessorFn: (row) => dayjs(row.updated_at).format('MMMM D, YYYY h:mma'),
enableSorting: false,
},
],
[]
);

View file

@ -503,7 +503,7 @@ const StreamsTable = ({}) => {
<>
<Tooltip label="Add to Channel">
<ActionIcon
size="sm"
size="xs"
color={theme.tailwind.blue[6]}
variant="transparent"
onClick={() => addStreamToChannel(row.original.id)}
@ -522,7 +522,7 @@ const StreamsTable = ({}) => {
<Tooltip label="Create New Channel">
<ActionIcon
size="sm"
size="xs"
color={theme.tailwind.green[5]}
variant="transparent"
onClick={() => createChannelFromStream(row.original)}
@ -533,7 +533,7 @@ const StreamsTable = ({}) => {
<Menu>
<Menu.Target>
<ActionIcon variant="transparent" size="sm">
<ActionIcon variant="transparent" size="xs">
<EllipsisVertical size="18" />
</ActionIcon>
</Menu.Target>