mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 10:45:27 +00:00
Remove old bulk create view.
This commit is contained in:
parent
8ab9a508d4
commit
4f49636899
2 changed files with 3 additions and 263 deletions
|
|
@ -698,266 +698,6 @@ class ChannelViewSet(viewsets.ModelViewSet):
|
|||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method="post",
|
||||
operation_description=(
|
||||
"Bulk create channels from existing streams. For each object, if 'channel_number' is provided, "
|
||||
"it is used (if available); otherwise, the next available number is auto-assigned. "
|
||||
"Each object must include 'stream_id' and 'name'. "
|
||||
"Supports single profile ID or array of profile IDs in 'channel_profile_ids'."
|
||||
),
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=["stream_id"],
|
||||
properties={
|
||||
"stream_id": openapi.Schema(
|
||||
type=openapi.TYPE_INTEGER,
|
||||
description="ID of the stream to link",
|
||||
),
|
||||
"channel_number": openapi.Schema(
|
||||
type=openapi.TYPE_NUMBER,
|
||||
description="(Optional) Desired channel number. Must not be in use.",
|
||||
),
|
||||
"name": openapi.Schema(
|
||||
type=openapi.TYPE_STRING, description="Desired channel name"
|
||||
),
|
||||
"channel_profile_ids": openapi.Schema(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=openapi.Items(type=openapi.TYPE_INTEGER),
|
||||
description="(Optional) Channel profile ID(s) to add the channel to. Can be a single ID or array of IDs."
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
responses={201: "Bulk channels created"},
|
||||
)
|
||||
@action(detail=False, methods=["post"], url_path="from-stream/bulk")
|
||||
def from_stream_bulk(self, request):
|
||||
data_list = request.data
|
||||
if not isinstance(data_list, list):
|
||||
return Response(
|
||||
{"error": "Expected a list of channel objects"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
created_channels = []
|
||||
errors = []
|
||||
|
||||
# Gather current used numbers once.
|
||||
used_numbers = set(
|
||||
Channel.objects.all().values_list("channel_number", flat=True)
|
||||
)
|
||||
next_number = 1
|
||||
|
||||
def get_auto_number():
|
||||
nonlocal next_number
|
||||
while next_number in used_numbers:
|
||||
next_number += 1
|
||||
used_numbers.add(next_number)
|
||||
return next_number
|
||||
|
||||
logos_to_create = []
|
||||
channels_to_create = []
|
||||
streams_map = []
|
||||
logo_map = []
|
||||
profile_map = [] # Track which profiles each channel should be added to
|
||||
|
||||
for item in data_list:
|
||||
stream_id = item.get("stream_id")
|
||||
if not stream_id:
|
||||
errors.append(
|
||||
{
|
||||
"item": item,
|
||||
"error": "Missing required field: stream_id is required.",
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
stream = get_object_or_404(Stream, pk=stream_id)
|
||||
except Exception as e:
|
||||
errors.append({"item": item, "error": str(e)})
|
||||
continue
|
||||
|
||||
name = item.get("name")
|
||||
if name is None:
|
||||
name = stream.name
|
||||
|
||||
channel_group = stream.channel_group
|
||||
|
||||
stream_custom_props = stream.custom_properties or {}
|
||||
|
||||
channel_number = None
|
||||
if "tvg-chno" in stream_custom_props:
|
||||
channel_number = float(stream_custom_props["tvg-chno"])
|
||||
elif "channel-number" in stream_custom_props:
|
||||
channel_number = float(stream_custom_props["channel-number"])
|
||||
elif "num" in stream_custom_props:
|
||||
channel_number = float(stream_custom_props["num"])
|
||||
# Get the tvc_guide_stationid from custom properties if it exists
|
||||
tvc_guide_stationid = None
|
||||
if "tvc-guide-stationid" in stream_custom_props:
|
||||
tvc_guide_stationid = stream_custom_props["tvc-guide-stationid"]
|
||||
|
||||
# Determine channel number: if provided, use it (if free); else auto assign.
|
||||
if channel_number is None:
|
||||
provided_number = item.get("channel_number")
|
||||
if provided_number is None:
|
||||
channel_number = get_auto_number()
|
||||
else:
|
||||
try:
|
||||
channel_number = float(provided_number)
|
||||
except ValueError:
|
||||
errors.append(
|
||||
{
|
||||
"item": item,
|
||||
"error": "channel_number must be a number.",
|
||||
}
|
||||
)
|
||||
continue
|
||||
if (
|
||||
channel_number in used_numbers
|
||||
or Channel.objects.filter(
|
||||
channel_number=channel_number
|
||||
).exists()
|
||||
):
|
||||
errors.append(
|
||||
{
|
||||
"item": item,
|
||||
"error": f"Channel number {channel_number} is already in use.",
|
||||
}
|
||||
)
|
||||
continue
|
||||
used_numbers.add(channel_number)
|
||||
|
||||
channel_data = {
|
||||
"channel_number": channel_number,
|
||||
"name": name,
|
||||
"tvc_guide_stationid": tvc_guide_stationid,
|
||||
"tvg_id": stream.tvg_id,
|
||||
}
|
||||
|
||||
# Only add channel_group_id if the stream has a channel group
|
||||
if channel_group:
|
||||
channel_data["channel_group_id"] = channel_group.id
|
||||
|
||||
# Attempt to find existing EPGs with the same tvg-id
|
||||
epgs = EPGData.objects.filter(tvg_id=stream.tvg_id)
|
||||
if epgs:
|
||||
channel_data["epg_data_id"] = epgs.first().id
|
||||
|
||||
serializer = self.get_serializer(data=channel_data)
|
||||
if serializer.is_valid():
|
||||
validated_data = serializer.validated_data
|
||||
channel = Channel(**validated_data)
|
||||
channels_to_create.append(channel)
|
||||
|
||||
streams_map.append([stream_id])
|
||||
# Store which profiles this channel should be added to - normalize to array
|
||||
channel_profile_ids = item.get("channel_profile_ids")
|
||||
if channel_profile_ids is not None:
|
||||
# Normalize single ID to array
|
||||
if not isinstance(channel_profile_ids, list):
|
||||
channel_profile_ids = [channel_profile_ids]
|
||||
|
||||
profile_map.append(channel_profile_ids)
|
||||
|
||||
if stream.logo_url:
|
||||
logos_to_create.append(
|
||||
Logo(
|
||||
url=stream.logo_url,
|
||||
name=stream.name or stream.tvg_id,
|
||||
)
|
||||
)
|
||||
logo_map.append(stream.logo_url)
|
||||
else:
|
||||
logo_map.append(None)
|
||||
|
||||
else:
|
||||
errors.append({"item": item, "error": serializer.errors})
|
||||
|
||||
if logos_to_create:
|
||||
Logo.objects.bulk_create(logos_to_create, ignore_conflicts=True)
|
||||
|
||||
channel_logos = {
|
||||
logo.url: logo
|
||||
for logo in Logo.objects.filter(
|
||||
url__in=[url for url in logo_map if url is not None]
|
||||
)
|
||||
}
|
||||
|
||||
# Get all profiles for default assignment
|
||||
all_profiles = ChannelProfile.objects.all()
|
||||
channel_profile_memberships = []
|
||||
|
||||
if channels_to_create:
|
||||
with transaction.atomic():
|
||||
created_channels = Channel.objects.bulk_create(channels_to_create)
|
||||
|
||||
update = []
|
||||
for channel, stream_ids, logo_url, channel_profile_ids in zip(
|
||||
created_channels, streams_map, logo_map, profile_map
|
||||
):
|
||||
if logo_url:
|
||||
channel.logo = channel_logos[logo_url]
|
||||
update.append(channel)
|
||||
|
||||
# Handle channel profile membership based on channel_profile_ids
|
||||
if channel_profile_ids:
|
||||
# Add channel only to the specified profiles
|
||||
try:
|
||||
specific_profiles = ChannelProfile.objects.filter(id__in=channel_profile_ids)
|
||||
channel_profile_memberships.extend([
|
||||
ChannelProfileMembership(
|
||||
channel_profile=profile,
|
||||
channel=channel,
|
||||
enabled=True
|
||||
)
|
||||
for profile in specific_profiles
|
||||
])
|
||||
except Exception:
|
||||
# If profiles don't exist, add to all profiles as fallback
|
||||
channel_profile_memberships.extend([
|
||||
ChannelProfileMembership(
|
||||
channel_profile=profile,
|
||||
channel=channel,
|
||||
enabled=True
|
||||
)
|
||||
for profile in all_profiles
|
||||
])
|
||||
else:
|
||||
# Default behavior: add to all profiles
|
||||
channel_profile_memberships.extend([
|
||||
ChannelProfileMembership(
|
||||
channel_profile=profile,
|
||||
channel=channel,
|
||||
enabled=True
|
||||
)
|
||||
for profile in all_profiles
|
||||
])
|
||||
|
||||
# Bulk create profile memberships
|
||||
if channel_profile_memberships:
|
||||
ChannelProfileMembership.objects.bulk_create(
|
||||
channel_profile_memberships
|
||||
)
|
||||
|
||||
# Update logos
|
||||
if update:
|
||||
Channel.objects.bulk_update(update, ["logo"])
|
||||
|
||||
# Set stream relationships
|
||||
for channel, stream_ids in zip(created_channels, streams_map):
|
||||
channel.streams.set(stream_ids)
|
||||
|
||||
response_data = {"created": ChannelSerializer(created_channels, many=True).data}
|
||||
if errors:
|
||||
response_data["errors"] = errors
|
||||
|
||||
return Response(response_data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method="post",
|
||||
operation_description=(
|
||||
|
|
@ -983,8 +723,8 @@ class ChannelViewSet(viewsets.ModelViewSet):
|
|||
),
|
||||
responses={202: "Task started successfully"},
|
||||
)
|
||||
@action(detail=False, methods=["post"], url_path="from-stream/bulk-async")
|
||||
def from_stream_bulk_async(self, request):
|
||||
@action(detail=False, methods=["post"], url_path="from-stream/bulk")
|
||||
def from_stream_bulk(self, request):
|
||||
from .tasks import bulk_create_channels_from_streams
|
||||
|
||||
stream_ids = request.data.get("stream_ids", [])
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ export default class API {
|
|||
}
|
||||
|
||||
const response = await request(
|
||||
`${host}/api/channels/channels/from-stream/bulk-async/`,
|
||||
`${host}/api/channels/channels/from-stream/bulk/`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue