From 4b4ef80d236a99231e459aa83481bfec356db036 Mon Sep 17 00:00:00 2001
From: Ernesto Rico Schmidt <ernesto@helsinki.at>
Date: Tue, 4 Apr 2023 16:13:21 -0400
Subject: [PATCH] Extract generate_conflicts as function

---
 program/models.py   | 143 +-------------------------------------------
 program/services.py | 137 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 138 insertions(+), 142 deletions(-)

diff --git a/program/models.py b/program/models.py
index e2f317fa..2fb10313 100644
--- a/program/models.py
+++ b/program/models.py
@@ -24,15 +24,11 @@ from rest_framework.exceptions import ValidationError
 from versatileimagefield.fields import PPOIField, VersatileImageField
 
 from django.contrib.auth.models import User
-from django.core.exceptions import ObjectDoesNotExist
 from django.db import models
 from django.db.models import Q, QuerySet
 from django.utils.translation import gettext_lazy as _
-
 from program.utils import parse_datetime
-from steering.settings import (
-    THUMBNAIL_SIZES,
-)
+from steering.settings import THUMBNAIL_SIZES
 
 
 class ScheduleConflictError(ValidationError):
@@ -372,143 +368,6 @@ class Schedule(models.Model):
     class Meta:
         ordering = ("first_date", "start_time")
 
-    # FIXME: this does not belong here
-    @staticmethod
-    def generate_conflicts(timeslots):
-        """
-        Tests a list of timeslot objects for colliding timeslots in the database
-        Returns a list of conflicts containing dicts of projected timeslots, collisions and
-        solutions
-        """
-
-        conflicts = {}
-        projected = []
-        solutions = {}
-
-        # Cycle each timeslot
-        for ts in timeslots:
-            # Contains collisions
-            collisions = []
-
-            # Contains possible solutions
-            solution_choices = set()
-
-            # Get collisions for each timeslot
-            collision_list = list(TimeSlot.objects.get_colliding_timeslots(ts).order_by("start"))
-
-            # Add the projected timeslot
-            projected_entry = {
-                "hash": ts.hash,
-                "start": str(ts.start),
-                "end": str(ts.end),
-            }
-
-            for c in collision_list:
-                # Add the collision
-                collision = {
-                    "id": c.id,
-                    "start": str(c.start),
-                    "end": str(c.end),
-                    "playlist_id": c.playlist_id,
-                    "show": c.show.id,
-                    "show_name": c.show.name,
-                    "schedule": c.schedule_id,
-                    "memo": c.memo,
-                }
-
-                # Get note
-                try:
-                    note = Note.objects.get(timeslot=c.id)
-                    collision["note_id"] = note.pk
-                except ObjectDoesNotExist:
-                    collision["note_id"] = None
-
-                collisions.append(collision)
-
-                """Determine acceptable solutions"""
-
-                if len(collision_list) > 1:
-                    # If there is more than one collision: Only these two are supported at the
-                    # moment
-                    solution_choices.add("theirs")
-                    solution_choices.add("ours")
-                else:
-                    # These two are always possible: Either keep theirs and remove ours or vice
-                    # versa
-                    solution_choices.add("theirs")
-                    solution_choices.add("ours")
-
-                    # Partly overlapping: projected starts earlier than existing and ends earlier
-                    #
-                    #    ex.  pr.
-                    #        +--+
-                    #        |  |
-                    #   +--+ |  |
-                    #   |  | +--+
-                    #   |  |
-                    #   +--+
-                    #
-                    if ts.end > c.start > ts.start <= c.end:
-                        solution_choices.add("theirs-end")
-                        solution_choices.add("ours-end")
-
-                    # Partly overlapping: projected starts later than existing and ends later
-                    #
-                    #    ex.  pr.
-                    #   +--+
-                    #   |  |
-                    #   |  | +--+
-                    #   +--+ |  |
-                    #        |  |
-                    #        +--+
-                    #
-                    if c.start <= ts.start < c.end < ts.end:
-                        solution_choices.add("theirs-start")
-                        solution_choices.add("ours-start")
-
-                    # Fully overlapping: projected starts earlier and ends later than existing
-                    #
-                    #    ex.  pr.
-                    #        +--+
-                    #   +--+ |  |
-                    #   |  | |  |
-                    #   +--+ |  |
-                    #        +--+
-                    #
-                    if ts.start < c.start and ts.end > c.end:
-                        solution_choices.add("theirs-end")
-                        solution_choices.add("theirs-start")
-                        solution_choices.add("theirs-both")
-
-                    # Fully overlapping: projected starts later and ends earlier than existing
-                    #
-                    #    ex.  pr.
-                    #   +--+
-                    #   |  | +--+
-                    #   |  | |  |
-                    #   |  | +--+
-                    #   +--+
-                    #
-                    if ts.start > c.start and ts.end < c.end:
-                        solution_choices.add("ours-end")
-                        solution_choices.add("ours-start")
-                        solution_choices.add("ours-both")
-
-            if len(collisions) > 0:
-                solutions[ts.hash] = ""
-
-            projected_entry["collisions"] = collisions
-            projected_entry["solution_choices"] = solution_choices
-            projected_entry["error"] = None
-            projected.append(projected_entry)
-
-        conflicts["projected"] = projected
-        conflicts["solutions"] = solutions
-        conflicts["notes"] = {}
-        conflicts["playlists"] = {}
-
-        return conflicts
-
 
 class TimeSlotManager(models.Manager):
     @staticmethod
diff --git a/program/services.py b/program/services.py
index 09dc7295..eed89ef9 100644
--- a/program/services.py
+++ b/program/services.py
@@ -545,3 +545,140 @@ def generate_timeslots(schedule):
         )
 
     return timeslots
+
+
+# TODO: add type annotations
+def generate_conflicts(timeslots):
+    """
+    Tests a list of timeslot objects for colliding timeslots in the database
+    Returns a list of conflicts containing dicts of projected timeslots, collisions and
+    solutions
+    """
+
+    conflicts = {}
+    projected = []
+    solutions = {}
+
+    # Cycle each timeslot
+    for ts in timeslots:
+        # Contains collisions
+        collisions = []
+
+        # Contains possible solutions
+        solution_choices = set()
+
+        # Get collisions for each timeslot
+        collision_list = list(TimeSlot.objects.get_colliding_timeslots(ts).order_by("start"))
+
+        # Add the projected timeslot
+        projected_entry = {
+            "hash": ts.hash,
+            "start": str(ts.start),
+            "end": str(ts.end),
+        }
+
+        for c in collision_list:
+            # Add the collision
+            collision = {
+                "id": c.id,
+                "start": str(c.start),
+                "end": str(c.end),
+                "playlist_id": c.playlist_id,
+                "show": c.show.id,
+                "show_name": c.show.name,
+                "schedule": c.schedule_id,
+                "memo": c.memo,
+            }
+
+            # Get note
+            try:
+                note = Note.objects.get(timeslot=c.id)
+                collision["note_id"] = note.pk
+            except ObjectDoesNotExist:
+                collision["note_id"] = None
+
+            collisions.append(collision)
+
+            """Determine acceptable solutions"""
+
+            if len(collision_list) > 1:
+                # If there is more than one collision: Only these two are supported at the
+                # moment
+                solution_choices.add("theirs")
+                solution_choices.add("ours")
+            else:
+                # These two are always possible: Either keep theirs and remove ours or vice
+                # versa
+                solution_choices.add("theirs")
+                solution_choices.add("ours")
+
+                # Partly overlapping: projected starts earlier than existing and ends earlier
+                #
+                #    ex.  pr.
+                #        +--+
+                #        |  |
+                #   +--+ |  |
+                #   |  | +--+
+                #   |  |
+                #   +--+
+                #
+                if ts.end > c.start > ts.start <= c.end:
+                    solution_choices.add("theirs-end")
+                    solution_choices.add("ours-end")
+
+                # Partly overlapping: projected starts later than existing and ends later
+                #
+                #    ex.  pr.
+                #   +--+
+                #   |  |
+                #   |  | +--+
+                #   +--+ |  |
+                #        |  |
+                #        +--+
+                #
+                if c.start <= ts.start < c.end < ts.end:
+                    solution_choices.add("theirs-start")
+                    solution_choices.add("ours-start")
+
+                # Fully overlapping: projected starts earlier and ends later than existing
+                #
+                #    ex.  pr.
+                #        +--+
+                #   +--+ |  |
+                #   |  | |  |
+                #   +--+ |  |
+                #        +--+
+                #
+                if ts.start < c.start and ts.end > c.end:
+                    solution_choices.add("theirs-end")
+                    solution_choices.add("theirs-start")
+                    solution_choices.add("theirs-both")
+
+                # Fully overlapping: projected starts later and ends earlier than existing
+                #
+                #    ex.  pr.
+                #   +--+
+                #   |  | +--+
+                #   |  | |  |
+                #   |  | +--+
+                #   +--+
+                #
+                if ts.start > c.start and ts.end < c.end:
+                    solution_choices.add("ours-end")
+                    solution_choices.add("ours-start")
+                    solution_choices.add("ours-both")
+
+        if len(collisions) > 0:
+            solutions[ts.hash] = ""
+
+        projected_entry["collisions"] = collisions
+        projected_entry["solution_choices"] = solution_choices
+        projected_entry["error"] = None
+        projected.append(projected_entry)
+
+    conflicts["projected"] = projected
+    conflicts["solutions"] = solutions
+    conflicts["notes"] = {}
+    conflicts["playlists"] = {}
+
+    return conflicts
-- 
GitLab