diff --git a/program/serializers.py b/program/serializers.py
index 2fd809b7e7246b39e6568a6240e21763f79e0f4f..c7221805f14852c47df905bd871d3e7b881f7da1 100644
--- a/program/serializers.py
+++ b/program/serializers.py
@@ -23,7 +23,9 @@ from typing import List, TypedDict
 
 from drf_jsonschema_serializer import JSONSchemaField
 from rest_framework import serializers
+from rest_framework.permissions import exceptions
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils import timezone
@@ -358,22 +360,58 @@ class HostSerializer(serializers.ModelSerializer):
     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.
         """
 
-        instance.biography = validated_data.get("biography", instance.biography)
-        instance.email = validated_data.get("email", instance.email)
-        instance.image = validated_data.get("image_id", instance.image)
-        instance.is_active = validated_data.get("is_active", instance.is_active)
-        instance.name = validated_data.get("name", instance.name)
+        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 = [
+            permission.split("__")[-1]
+            for permission in user.get_all_permissions()
+            if permission.startswith("program.edit__host")
+        ]
+
+        # Only privileged users and owners of a host with edit permissions are allowed to update it
+        # Being a privileged user overrides the ownership
+        if not (user_is_privileged or (user_is_owner and len(user_edit_permissions) > 0)):
+            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 := validated_data.get("biography")
+        ) and "biography" not in user_edit_permissions:
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to edit the host’s biography."
+            )
+        else:
+            instance.biography = biography
 
-        if links_data := validated_data.get("links"):
-            instance = delete_links(instance)
+        if (name := validated_data.get("name")) and "name" not in user_edit_permissions:
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to edit the host’s name."
+            )
+        else:
+            instance.name = name
 
-            for link_data in links_data:
-                HostLink.objects.create(host=instance, **link_data)
+        # Only update these fields if the user is privileged, ignore otherwise
+        if user_is_privileged:
+            instance.email = validated_data.get("email", instance.email)
+            instance.image = validated_data.get("image_id", instance.image)
+            instance.is_active = validated_data.get("is_active", instance.is_active)
+
+            # optional nested objects
+            if links_data := validated_data.get("links"):
+                instance = delete_links(instance)
 
-        if owners := validated_data.get("owners", []):
-            instance.owners.set(owners)
+                for link_data in links_data:
+                    HostLink.objects.create(host=instance, **link_data)
+
+            # optional many-to-many
+            if owners := validated_data.get("owners"):
+                instance.owners.set(owners)
 
         instance.updated_by = self.context.get("request").user.username
 
@@ -534,54 +572,95 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer):
     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.
         """
 
-        instance.cba_series_id = validated_data.get("cba_series_id", instance.cba_series_id)
-        instance.default_playlist_id = validated_data.get(
-            "default_playlist_id", instance.default_playlist_id
-        )
-        instance.description = validated_data.get("description", instance.description)
-        instance.email = validated_data.get("email", instance.email)
-        instance.funding_category = validated_data.get(
-            "funding_category_id", instance.funding_category
-        )
-        instance.image = validated_data.get("image_id", instance.image)
-        instance.internal_note = validated_data.get("internal_note", instance.internal_note)
-        instance.is_active = validated_data.get("is_active", instance.is_active)
-        instance.is_public = validated_data.get("is_public", instance.is_public)
-        instance.logo = validated_data.get("logo_id", instance.logo)
-        instance.name = validated_data.get("name", instance.name)
-        instance.predecessor = validated_data.get("predecessor_id", instance.predecessor)
-        instance.short_description = validated_data.get(
-            "short_description", instance.short_description
-        )
-        instance.slug = validated_data.get("slug", instance.slug)
-        instance.type = validated_data.get("type_id", instance.type)
+        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 = [
+            permission.split("__")[-1]
+            for permission in user.get_all_permissions()
+            if permission.startswith("program.edit__show")
+        ]
+
+        # Only privileged users and owners of a show with edit permissions are allowed to update it
+        # Being a privileged user overrides the ownership
+        if not (user_is_privileged or (user_is_owner and len(user_edit_permissions) > 0)):
+            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 := validated_data.get("description")
+        ) and "description" not in user_edit_permissions:
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to edit the show’s description."
+            )
+        else:
+            instance.description = description
 
-        # optional many-to-many in PATCH requests
-        if (category := validated_data.get("category")) is not None:
-            instance.category.set(category)
+        if (name := validated_data.get("name")) and "name" not in user_edit_permissions:
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to edit the show’s name."
+            )
+        else:
+            instance.name = name
 
-        if (hosts := validated_data.get("hosts")) is not None:
-            instance.hosts.set(hosts)
+        if (
+            short_description := validated_data.get("short_description")
+        ) and "short_description" not in user_edit_permissions:
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to edit the show’s short description."
+            )
+        else:
+            instance.short_description = short_description
 
-        if (language := validated_data.get("language")) is not None:
-            instance.language.set(language)
+        # Only update these fields if the user is privileged, ignore otherwise
+        if user_is_privileged:
+            instance.cba_series_id = validated_data.get("cba_series_id", instance.cba_series_id)
+            instance.default_playlist_id = validated_data.get(
+                "default_playlist_id", instance.default_playlist_id
+            )
+            instance.email = validated_data.get("email", instance.email)
+            instance.funding_category = validated_data.get(
+                "funding_category_id", instance.funding_category
+            )
+            instance.image = validated_data.get("image_id", instance.image)
+            instance.internal_note = validated_data.get("interna_note", instance.internal_note)
+            instance.is_active = validated_data.get("is_active", instance.is_active)
+            instance.is_public = validated_data.get("is_public", instance.is_public)
+            instance.logo = validated_data.get("logo", instance.logo)
+            instance.predecessor = validated_data.get("predecessor", instance.predecessor)
+            instance.slug = validated_data.get("slug", instance.slug)
+            instance.type = validated_data.get("type", instance.type)
 
-        if (music_focus := validated_data.get("music_focus")) is not None:
-            instance.music_focus.set(music_focus)
+            # optional many-to-many
+            if category := validated_data.get("category"):
+                instance.category.set(category)
 
-        if (owners := validated_data.get("owners")) is not None:
-            instance.owners.set(owners)
+            if hosts := validated_data.get("hosts"):
+                instance.hosts.set(hosts)
 
-        if (topic := validated_data.get("topic")) is not None:
-            instance.topic.set(topic)
+            if language := validated_data.get("language"):
+                instance.language.set(language)
 
-        if links_data := validated_data.get("links"):
-            instance = delete_links(instance)
+            if music_focus := validated_data.get("music_focus"):
+                instance.music_focus.set(music_focus)
 
-            for link_data in links_data:
-                ShowLink.objects.create(show=instance, **link_data)
+            if owners := validated_data.get("owners"):
+                instance.owners.set(owners)
+
+            if topic := validated_data.get("topic"):
+                instance.topic.set(topic)
+
+            # optional nested objects
+            if links_data := validated_data.get("links"):
+                instance = delete_links(instance)
+
+                for link_data in links_data:
+                    ShowLink.objects.create(show=instance, **link_data)
 
         instance.updated_by = self.context.get("request").user.username
 
@@ -858,7 +937,11 @@ class NoteSerializer(serializers.ModelSerializer):
         ) + read_only_fields
 
     def create(self, validated_data):
-        """Create and return a new Note instance, given the 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.
+        """
 
         links_data = validated_data.pop("links", [])
 
@@ -871,9 +954,19 @@ class NoteSerializer(serializers.ModelSerializer):
             validated_data["timeslot"] = validated_data.pop("timeslot_id", timeslot_pk)
 
         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 privileged users and owners of a show are allowed to create a note
+        # Being a privileged user overrides the ownership
+        if not (user_is_privileged or user_is_owner):
+            raise exceptions.PermissionDenied(
+                detail="You are not allowed to create a note for this show."
+            )
 
         # we derive `contributors`, `language` and `topic` from the Show's values if not set
-        contributors = validated_data.pop("contributors", show.owners.values_list("id", flat=True))
+        contributors = validated_data.pop("contributors", show.hosts.values_list("id", flat=True))
         language = validated_data.pop("language", show.language.values_list("id", flat=True))
         topic = validated_data.pop("topic", show.topic.values_list("id", flat=True))
 
@@ -903,7 +996,21 @@ class NoteSerializer(serializers.ModelSerializer):
         return note
 
     def update(self, instance, validated_data):
-        """Update and return an existing Note instance, given the 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.
+        """
+
+        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
+
+        # Only privileged users and owners of a show are allowed to update a note
+        # Being a privileged user overrides the ownership
+        if not (user_is_privileged or user_is_owner):
+            raise exceptions.PermissionDenied(detail="You are not allowed to update this note.")
 
         instance.cba_id = validated_data.get("cba_id", instance.cba_id)
         instance.content = validated_data.get("content", instance.content)
@@ -914,14 +1021,15 @@ class NoteSerializer(serializers.ModelSerializer):
         instance.tags = validated_data.get("tags", instance.tags)
         instance.title = validated_data.get("title", instance.title)
 
-        # optional many-to-many in PATCH requests
-        if (contributors := validated_data.get("contributors")) is not None:
+        # optional many-to-many
+        if contributors := validated_data.get("contributors"):
             instance.contributors.set(contributors)
 
-        if (language := validated_data.get("language")) is not None:
+        if language := validated_data.get("language"):
             instance.language.set(language)
 
-        if (topic := validated_data.get("topic")) is not None:
+        # Only update this field if the user is privileged, ignore otherwise
+        if topic := validated_data.get("topic") and user_is_privileged:
             instance.topic.set(topic)
 
         if cba_id := validated_data.get("cba_id"):