diff --git a/program/serializers.py b/program/serializers.py
index 30adfc823d96af25cc92e22cc80f8c0b1969ba86..f4cd9b2cfba9c16dc16f1eb656fa6e4b57b4c8b4 100644
--- a/program/serializers.py
+++ b/program/serializers.py
@@ -134,7 +134,6 @@ class UserSerializer(serializers.ModelSerializer):
 
     @staticmethod
     def get_is_privileged(obj: User) -> bool:
-        # return obj.groups.filter(name=settings.PRIVILEGED_GROUP).exists()
         return obj.is_superuser
 
     def create(self, validated_data):
@@ -387,37 +386,27 @@ class HostSerializer(serializers.ModelSerializer):
         return host
 
     def update(self, instance, validated_data):
-        """
-        Update and return an existing Host instance, given the validated data.
-
-        A `PermissionDenied` exception will be raised if the user is not privileged or the owner of
-        the host and has the permissions to edit the fields.
-        """
+        """Update and return an existing Host instance, given the validated data."""
 
         user = self.context.get("request").user
-        # user_is_privileged = user.groups.filter(name=settings.PRIVILEGED_GROUP).exists()
         user_is_owner = user in instance.owners.all()
-        user_edit_permissions = [
+        user_permissions = set(
             permission.split("__")[-1]
             for permission in user.get_all_permissions()
             if permission.startswith("program.edit__host")
-        ]
+        )
+        update_fields = set(validated_data.keys())
 
-        # Only superusers and owners of a host with edit permissions are allowed to update it
-        # Being a superuser overrides the ownership
-        if not (user.is_superuser or (user_is_owner and len(user_edit_permissions) > 0)):
+        # having the update_host permission overrides the ownership
+        if not (user.has_perm("program.update_host") or (user_is_owner and user_permissions)):
             raise exceptions.PermissionDenied(detail="You are not allowed to update this host.")
 
-        # Only users with edit permissions are allowed to edit these fields
-        if "biography" in validated_data and "biography" not in user_edit_permissions:
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to edit the host’s biography."
-            )
-
-        if "name" in validated_data and "name" not in user_edit_permissions:
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to edit the host’s name."
-            )
+        # without the update_host permission, fields without edit permission are not allowed
+        if not user.has_perm("program.update_host") and (
+            not_allowed := update_fields.difference(user_permissions)
+        ):
+            detail = {field: "You are not allowed to edit this field" for field in not_allowed}
+            raise exceptions.PermissionDenied(detail=detail)
 
         if "biography" in validated_data:
             instance.biography = validated_data.get("biography")
@@ -425,27 +414,25 @@ class HostSerializer(serializers.ModelSerializer):
         if "name" in validated_data:
             instance.name = validated_data.get("name")
 
-        # Only update these fields if the user superuser, ignore otherwise
-        if user.is_superuser:
-            if "email" in validated_data:
-                instance.email = validated_data.get("email")
+        if "email" in validated_data:
+            instance.email = validated_data.get("email")
 
-            if "image" in validated_data:
-                instance.image = validated_data.get("image")
+        if "image" in validated_data:
+            instance.image = validated_data.get("image")
 
-            if "is_active" in validated_data:
-                instance.is_active = validated_data.get("is_active")
+        if "is_active" in validated_data:
+            instance.is_active = validated_data.get("is_active")
 
-            # optional nested objects
-            if links_data := validated_data.get("links"):
-                instance = delete_links(instance)
+        # optional many-to-many
+        if "owners" in validated_data:
+            instance.owners.set(validated_data.get("owners"))
 
-                for link_data in links_data:
-                    HostLink.objects.create(host=instance, **link_data)
+        # optional nested objects
+        if "links" in validated_data:
+            instance = delete_links(instance)
 
-            # optional many-to-many
-            if "owners" in validated_data.get("owners"):
-                instance.owners.set(validated_data.get("owners", []))
+            for link_data in validated_data.get("links"):
+                HostLink.objects.create(host=instance, **link_data)
 
         instance.updated_by = self.context.get("request").user.username
 
@@ -610,45 +597,27 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
         return show
 
     def update(self, instance, validated_data):
-        """
-        Update and return an existing Show instance, given the validated data.
-
-        A `PermissionDenied` exception will be raised if the user is not privileged or the owner of
-        a show and has the permissions to edit the fields.
-        """
+        """Update and return an existing Show instance, given the validated data."""
 
         user = self.context.get("request").user
-        # user_is_privileged = user.groups.filter(name=settings.PRIVILEGED_GROUP).exists()
-        user_is_owner = instance in user.shows.all()
-        user_edit_permissions = [
+        user_is_owner = user in instance.owners.all()
+        user_permissions = set(
             permission.split("__")[-1]
             for permission in user.get_all_permissions()
             if permission.startswith("program.edit__show")
-        ]
+        )
+        update_fields = set(validated_data.keys())
 
-        # Only superusers and owners of a show with edit permissions are allowed to update it
-        # Being a superuser overrides the ownership
-        if not (user.is_superuser or (user_is_owner and len(user_edit_permissions) > 0)):
+        # having update_show permission overrides the ownership
+        if not (user.has_perm("program.update_show") or (user_is_owner and user_permissions)):
             raise exceptions.PermissionDenied(detail="You are not allowed to update this show.")
 
-        # Only users with edit permissions are allowed to update these fields
-        if "description" in validated_data and "description" not in user_edit_permissions:
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to edit the show’s description."
-            )
-
-        if "name" in validated_data and "name" not in user_edit_permissions:
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to edit the show’s name."
-            )
-
-        if (
-            "short_description" in validated_data
-            and "short_description" not in user_edit_permissions
+        # without the update_show permission, fields without edit permission are not allowed
+        if not user.has_perm("update_show") and (
+            not_allowed := update_fields.difference(user_permissions)
         ):
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to edit the show’s short description."
-            )
+            detail = {field: "You are not allowed to edit this field" for field in not_allowed}
+            raise exceptions.PermissionDenied(detail=detail)
 
         if "description" in validated_data:
             instance.description = validated_data.get("description")
@@ -659,69 +628,67 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
         if "short_description" in validated_data:
             instance.short_description = validated_data.get("short_description")
 
-        # Only update these fields if the user is superuser, ignore otherwise
-        if user.is_superuser:
-            if "cba_series_id" in validated_data:
-                instance.cba_series_id = validated_data.get("cba_series_id")
+        if "cba_series_id" in validated_data:
+            instance.cba_series_id = validated_data.get("cba_series_id")
 
-            if "default_playlist_id" in validated_data:
-                instance.default_playlist_id = validated_data.get("default_playlist_id")
+        if "default_playlist_id" in validated_data:
+            instance.default_playlist_id = validated_data.get("default_playlist_id")
 
-            if "email" in validated_data:
-                instance.email = validated_data.get("email")
+        if "email" in validated_data:
+            instance.email = validated_data.get("email")
 
-            if "funding_category" in validated_data:
-                instance.funding_category = validated_data.get("funding_category")
+        if "funding_category" in validated_data:
+            instance.funding_category = validated_data.get("funding_category")
 
-            if "image" in validated_data:
-                instance.image = validated_data.get("image")
+        if "image" in validated_data:
+            instance.image = validated_data.get("image")
 
-            if "internal_note" in validated_data:
-                instance.internal_note = validated_data.get("internal_note")
+        if "internal_note" in validated_data:
+            instance.internal_note = validated_data.get("internal_note")
 
-            if "is_active" in validated_data:
-                instance.is_active = validated_data.get("is_active")
+        if "is_active" in validated_data:
+            instance.is_active = validated_data.get("is_active")
 
-            if "is_public" in validated_data:
-                instance.is_public = validated_data.get("is_public")
+        if "is_public" in validated_data:
+            instance.is_public = validated_data.get("is_public")
 
-            if "logo" in validated_data:
-                instance.logo = validated_data.get("logo")
+        if "logo" in validated_data:
+            instance.logo = validated_data.get("logo")
 
-            if "predecessor" in validated_data:
-                instance.predecessor = validated_data.get("predecessor")
+        if "predecessor" in validated_data:
+            instance.predecessor = validated_data.get("predecessor")
 
-            if "slug" in validated_data:
-                instance.slug = validated_data.get("slug")
+        if "slug" in validated_data:
+            instance.slug = validated_data.get("slug")
 
-            if "type" in validated_data:
-                instance.type = validated_data.get("type")
+        if "type" in validated_data:
+            instance.type = validated_data.get("type")
 
-            # optional many-to-many
-            if "category" in validated_data:
-                instance.category.set(validated_data.get("category", []))
+        # optional many-to-many
+        if "category" in validated_data:
+            instance.category.set(validated_data.get("category", []))
 
-            if "hosts" in validated_data:
-                instance.hosts.set(validated_data.get("hosts", []))
+        if "hosts" in validated_data:
+            instance.hosts.set(validated_data.get("hosts", []))
 
-            if "language" in validated_data:
-                instance.language.set(validated_data.get("language", []))
+        if "language" in validated_data:
+            instance.language.set(validated_data.get("language", []))
 
-            if "music_focus" in validated_data:
-                instance.music_focus.set(validated_data.get("music_focus", []))
+        if "music_focus" in validated_data:
+            instance.music_focus.set(validated_data.get("music_focus", []))
 
-            if "owners" in validated_data:
-                instance.owners.set(validated_data.get("owners", []))
+        if "owners" in validated_data:
+            instance.owners.set(validated_data.get("owners", []))
 
-            if "topic" in validated_data:
-                instance.topic.set(validated_data.get("topic", []))
+        if "topic" in validated_data:
+            instance.topic.set(validated_data.get("topic", []))
 
-            # optional nested objects
-            if links_data := validated_data.get("links"):
-                instance = delete_links(instance)
+        # optional nested objects
+        if "links" in validated_data:
+            instance = delete_links(instance)
 
-                for link_data in links_data:
-                    ShowLink.objects.create(show=instance, **link_data)
+            for link_data in validated_data.get("links"):
+                ShowLink.objects.create(host=instance, **link_data)
 
         instance.updated_by = self.context.get("request").user.username
 
@@ -1006,11 +973,7 @@ class NoteSerializer(serializers.ModelSerializer):
         ) + read_only_fields
 
     def create(self, validated_data):
-        """Create and return a new Note instance, given the validated data.
-
-        A `PermissionDenied` exception will be raised if the user is not privileged or the owner of
-        the show.
-        """
+        """Create and return a new Note instance, given the validated data."""
 
         links_data = validated_data.pop("links", [])
 
@@ -1022,15 +985,11 @@ class NoteSerializer(serializers.ModelSerializer):
 
         show = validated_data["timeslot"].schedule.show
         user = self.context.get("request").user
-        # user_is_privileged = user.groups.filter(name=settings.PRIVILEGED_GROUP).exists()
         user_is_owner = user in show.owners.all()
 
-        # Only superusers and owners of a show are allowed to create a note
-        # Being a superuser overrides the ownership
-        if not (user.is_superuser or user_is_owner):
-            raise exceptions.PermissionDenied(
-                detail="You are not allowed to create a note for this show."
-            )
+        # Having the create_note permission overrides the ownership
+        if not (user.has_perm("program.create_note") or user_is_owner):
+            raise exceptions.PermissionDenied(detail="You are not allowed to create this note.")
 
         # we derive `contributors`, `language` and `topic` from the Show's values if not set
         contributors = validated_data.pop("contributors", show.hosts.values_list("id", flat=True))
@@ -1058,19 +1017,13 @@ class NoteSerializer(serializers.ModelSerializer):
         return note
 
     def update(self, instance, validated_data):
-        """Update and return an existing Note instance, given the validated data.
-
-        A `PermissionDenied` exception will be raised if the user is not privileged or the owner of
-        a show.
-        """
+        """Update and return an existing Note instance, given the validated data."""
 
         user = self.context.get("request").user
-        # user_is_privileged = user.groups.filter(name=settings.PRIVILEGED_GROUP).exists()
         user_is_owner = user in instance.timeslot.schedule.show.owners.all()
 
-        # Only superusers and owners of a show are allowed to update a note
-        # Being a superuser overrides the ownership
-        if not (user.is_superuser or user_is_owner):
+        # Having the update_note permission overrides the ownership
+        if not (user.has_perm("program.update_note") or user_is_owner):
             raise exceptions.PermissionDenied(detail="You are not allowed to update this note.")
 
         if "cba_id" in validated_data:
@@ -1101,15 +1054,15 @@ class NoteSerializer(serializers.ModelSerializer):
         if "language" in validated_data:
             instance.language.set(validated_data.get("language", []))
 
-        # Only update this field if the user is superuser, ignore otherwise
-        if "topic" in validated_data and user.is_superuser:
+        # Only update this field if the user has the update_note permission, ignore otherwise
+        if "topic" in validated_data and user.has_perm("program.update_note"):
             instance.topic.set(validated_data.get("topic", []))
 
         # optional nested objects
-        if links_data := validated_data.get("links"):
+        if "links" in validated_data:
             instance = delete_links(instance)
 
-            for link_data in links_data:
+            for link_data in validated_data.get("links"):
                 NoteLink.objects.create(note=instance, **link_data)
 
         instance.updated_by = self.context.get("request").user.username