diff --git a/src/videoag_common/database/__init__.py b/src/videoag_common/database/__init__.py
index c29a9a09ca3aa6ecc33ce26155226d18029bb3cf..ad3625c9a1b5b4dc9a204127bbea7c4e106eb8d3 100644
--- a/src/videoag_common/database/__init__.py
+++ b/src/videoag_common/database/__init__.py
@@ -6,7 +6,7 @@ from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
 from sqlalchemy.sql.base import ExecutableOption
 from sqlalchemy.orm import Session as SessionDb, DeclarativeBase, Mapped, mapped_column, relationship
 from sqlalchemy.types import Text, String, TIMESTAMP
-from sqlalchemy import types, ForeignKey
+from sqlalchemy import types, ForeignKey, CheckConstraint, UniqueConstraint, ForeignKeyConstraint
 
 from .base import (
     Base,
diff --git a/src/videoag_common/database/drift_detector.py b/src/videoag_common/database/drift_detector.py
index e00a2cbdf6a22aa3d9c47c0cf1574f12db0db409..059af8e341391bcac8266be7b9663e517707e007 100644
--- a/src/videoag_common/database/drift_detector.py
+++ b/src/videoag_common/database/drift_detector.py
@@ -48,15 +48,15 @@ def _to_string(value) -> str:
         
         case PrimaryKeyConstraint() as constraint:
             assert isinstance(constraint, PrimaryKeyConstraint)
-            return f"PrimaryKeyConstraint({_to_string(constraint.columns)}, table.name={constraint.table.name})"
+            return f"PrimaryKeyConstraint({_to_string(constraint.columns)}, table.name='{constraint.table.name}')"
         
         case CheckConstraint() as constraint:
             assert isinstance(constraint, CheckConstraint)
-            return f"CheckConstraint({_to_string(constraint.columns)}, sqltext={constraint.sqltext} table.name={constraint.table.name})"
+            return f"CheckConstraint({_to_string(constraint.columns)}, sqltext='{constraint.sqltext}', table.name='{constraint.table.name}')"
         
         case UniqueConstraint() as constraint:
             assert isinstance(constraint, UniqueConstraint)
-            return f"UniqueConstraint({_to_string(constraint.columns)}, table.name={constraint.table.name})"
+            return f"UniqueConstraint({_to_string(constraint.columns)}, table.name='{constraint.table.name}')"
         
         case ForeignKeyConstraint() as constraint:
             assert isinstance(constraint, ForeignKeyConstraint)
@@ -112,9 +112,14 @@ def _check_constraint_equal(actual_constraint: Constraint, schema_constraint: Co
             
             if not isinstance(actual_constraint, CheckConstraint):
                 return False
-            if schema_constraint.sqltext != actual_constraint.sqltext:  # TODO test if this works
-                return False
-            return True
+            
+            # We try our best to check if they are equal
+            if schema_constraint.sqltext == actual_constraint.sqltext:
+                return True
+            # Sometimes the db returns it without correct brackets. Just strip them
+            if str(schema_constraint.sqltext).strip("()") == str(actual_constraint.sqltext).strip("()"):
+                return True
+            return False
         case ForeignKeyConstraint():
             assert isinstance(schema_constraint, ForeignKeyConstraint)  # For pycharm
             
@@ -342,14 +347,14 @@ def _check_table_equal(actual_table: Table, schema_table: Table) -> bool:
                 break
         else:
             correct = False
-            print(f"Missing constraint\n  {_to_string(schema_constraint)}\nin database. The following constraints do "
-                  f"not match:")
+            print(f"Missing constraint\n  {_to_string(schema_constraint)}\nin database for table {schema_table.name}. "
+                  f"The following constraints do not match:")
             print("\n".join(map(lambda c: f"  {_to_string(c)}", actual_table.constraints)))
     
     if len(unmatched_actual_constraints) > 0:
         correct = False
-        print(f"Got unexpected constraint\n  {_to_string(next(iter(unmatched_actual_constraints)))}\nin database. The "
-              f"following schema constraints do not match:")
+        print(f"Got unexpected constraint\n  {_to_string(next(iter(unmatched_actual_constraints)))}\nin database for "
+              f"table {schema_table.name}. The following schema constraints do not match:")
         print("\n".join(map(lambda c: f"  {_to_string(c)}", schema_table.constraints)))
     
     return correct
diff --git a/src/videoag_common/objects/job.py b/src/videoag_common/objects/job.py
index abaab0eaaad40c5cceb1e3fab147f332a4cb7752..5b9e5a1e9cb49b208f57bfb949714c6cecf2a4fb 100644
--- a/src/videoag_common/objects/job.py
+++ b/src/videoag_common/objects/job.py
@@ -22,6 +22,17 @@ _JOB_STATE_ENUM = create_enum_type(JobState)
 
 
 class Job(ApiObject, Base):
+    __table_args__ = (
+        CheckConstraint(
+            "(on_end_event_type IS NULL) = (on_end_event_data IS NULL)",
+            name="check_event",
+            comment="Event type and data must be both set or both null"
+        ),
+        CheckConstraint(
+            "cause_job_id IS NULL OR cause_user_id IS NULL",
+            name="check_only_one_cause"
+        )
+    )
 
     status: Mapped[JobState] = api_mapped(
         mapped_column(_JOB_STATE_ENUM, nullable=False, index=True, default=JobState.READY),
@@ -48,7 +59,6 @@ class Job(ApiObject, Base):
         )
     )
     
-    # TODO check both null or not null
     on_end_event_type: Mapped[str] = api_mapped(
         mapped_column(String(collation=STRING_COLLATION), nullable=True),
         ApiStringField(
@@ -79,7 +89,6 @@ class Job(ApiObject, Base):
             include_in_data=True
         )
     )
-    # TODO check only one
     cause_job_id: Mapped[int] = mapped_column(ForeignKey("job.id"), nullable=True, index=True)
     cause_user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=True, index=True)
     
diff --git a/src/videoag_common/objects/medium.py b/src/videoag_common/objects/medium.py
index f212e244146412063dd60e54fc41b33f48b666b4..29bedc2cd4e4c20142fa5e91fb198ee412c7c47b 100644
--- a/src/videoag_common/objects/medium.py
+++ b/src/videoag_common/objects/medium.py
@@ -6,12 +6,24 @@ from videoag_common.miscellaneous import *
 from videoag_common.api_object import *
 from videoag_common.media_process import *
 
-from .course import Lecture, Course
+from .course import Lecture
 from .job import Job
 
 
 # A file which was uploaded and needs to be assigned to a lecture, to be converted to a SourceFileTargetMedium
 class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
+    __table_args__ = (
+        CheckConstraint(
+            "NOT tag IS NULL OR lecture_id IS NULL",
+            name="check_tag_is_set",
+            comment="Tag must be set if this has a lecture id"
+        ),
+        CheckConstraint(
+            "NOT sha256 IS NULL OR lecture_id IS NULL",
+            name="check_sha256_is_set",
+            comment="sha256 must be set if this has a lecture id"
+        ),
+    )
     
     file_path: Mapped[str] = api_mapped(
         mapped_column(String(collation=STRING_COLLATION), nullable=False, index=True),  # Can't be unique because of deleted entries
@@ -41,6 +53,7 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
             include_in_data=True
         )
     )
+    lecture_id: Mapped[int] = mapped_column(ForeignKey("lecture.id"), nullable=True, index=True)
     sha256: Mapped[str] = api_mapped(
         mapped_column(String(length=64, collation=STRING_COLLATION), nullable=True),
         ApiStringField(
@@ -48,15 +61,8 @@ class SourceMedium(VisibilityApiObject, DeletableApiObject, Base):
             data_notes="Only calculated once this is sorted"
         )
     )
-    # TODO check course not null if lecture is not null
-    course_id: Mapped[int] = mapped_column(ForeignKey("course.id"), nullable=True, index=True)
-    lecture_id: Mapped[int] = mapped_column(ForeignKey("lecture.id"), nullable=True, index=True)
     tag: Mapped[str] = mapped_column(String(collation=STRING_COLLATION), nullable=True)
     
-    course: Mapped[Course] = relationship(
-        primaryjoin=lambda: Course.id == SourceMedium.course_id,
-        lazy="raise_on_sql"
-    )
     lecture: Mapped[Lecture] = relationship(
         back_populates="source_media",
         primaryjoin=lambda: Lecture.id == SourceMedium.lecture_id,