diff --git a/program/migrations/0012_auto_20180103_0054.py b/program/migrations/0012_auto_20180104_0005.py
similarity index 99%
rename from program/migrations/0012_auto_20180103_0054.py
rename to program/migrations/0012_auto_20180104_0005.py
index 8ba4be8ef317ef9547fe4d3bc8f06b74d2361dc9..a60764d665003ab1b799ca0c7de8ae878dcf19a9 100644
--- a/program/migrations/0012_auto_20180103_0054.py
+++ b/program/migrations/0012_auto_20180104_0005.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2018-01-02 23:54
+# Generated by Django 1.11.3 on 2018-01-03 23:05
 from __future__ import unicode_literals
 
 from django.conf import settings
diff --git a/program/templates/calendar.html b/program/templates/calendar.html
index 75b9e6ade8725237de295dce795802479a0a2a04..6f76d79b404e7f3e6c5a57e2aa8017b870409d95 100644
--- a/program/templates/calendar.html
+++ b/program/templates/calendar.html
@@ -128,17 +128,40 @@
     	notify.fadeOut(2000);
     }
 
+    /**
+     * Makes sure the csrftoken get submitted properly
+     * See https://stackoverflow.com/questions/35112451/forbidden-csrf-token-missing-or-incorrect-django-error
+     */
+    function getCookie(name) {
+        var cookieValue = null;
+        if (document.cookie && document.cookie != '') {
+            var cookies = document.cookie.split(';');
+            for (var i = 0; i < cookies.length; i++) {
+                var cookie = jQuery.trim(cookies[i]);
+                // Does this cookie string begin with the name we want?
+                if (cookie.substring(0, name.length + 1) == (name + '=')) {
+                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                    break;
+                }
+            }
+        }
+        return cookieValue;
+    }
+    var csrftoken = getCookie('csrftoken');
+    function csrfSafeMethod(method) {
+        // These HTTP methods do not require CSRF protection
+        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+    }
 
     jQuery(document).ready( function() {
 
        jQuery("#calendar").fullCalendar({
-          // Enable theme
-          theme: true,
+          theme: true, // Enable theme
           timezone: 'Europe/Berlin',
           locale: 'de', // TODO: make an option for that
           defaultView: 'agendaWeek',
           // Event dragging & resizing
-          editable: true,
+          editable: false,
           // Header
           header: {
             left: 'prev,next today',
@@ -155,16 +178,26 @@
                 if( ! confirm( "Wollen Sie diese Episode wirklich löschen?" ) )
                    return false;
 
-                // Delete from database
-                jQuery.post( '/api/v1/timeslots/' + event._id + '/', { 'action': 'delete_timeslot', 'id': event._id } )
-                .done(function( data ) {
-                   // Remove element from DOM
-                   jQuery('#calendar').fullCalendar('removeEvents', event._id );
-                   notify( 'Time slot deleted.' );
-                   console.log(data.result);
-                })
-                .fail(function( data ) {
-                   notify( data.result );
+                jQuery.ajaxSetup({
+                  beforeSend: function(xhr, settings) {
+                    if( ! csrfSafeMethod(settings.type) && ! this.crossDomain) {
+                      xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken') );
+                    }
+                  }
+                });
+
+                jQuery.ajax({
+                  url: '/api/v1/shows/' + event.show_id + '/timeslots/' + event._id + '/',
+                  type: 'DELETE',
+                  success: function(result) {
+                    jQuery('#calendar').fullCalendar('removeEvents', event._id );
+                    notify( 'Timeslot deleted.' );
+                  },
+                  error: function(request, msg, error) {
+                    notify( 'Delete failed: ' + request.responseJSON.detail );
+                    console.log(error)
+                    console.log(request)
+                  }
                 });
 
              });
diff --git a/program/views.py b/program/views.py
index dfe7ee74bf1268a8e9f4f96d60113666164a9d30..55737bb4d8b0d67e9b2fe7ebbc799f5d8c781dab 100644
--- a/program/views.py
+++ b/program/views.py
@@ -13,9 +13,11 @@ from django.views.generic.list import ListView
 from rest_framework import permissions, serializers, status, viewsets
 from rest_framework.views import APIView
 from rest_framework.response import Response
+from rest_framework.renderers import JSONRenderer
 from rest_framework.pagination import LimitOffsetPagination
+from rest_framework.decorators import api_view
 
-from program.models import Type, MusicFocus, Language, Note, Show, Category, RTRCategory, Topic, TimeSlot, Host, Schedule
+from program.models import Type, MusicFocus, Language, Note, Show, Category, RTRCategory, Topic, TimeSlot, Host, Schedule, RRule
 from program.serializers import TypeSerializer, LanguageSerializer, MusicFocusSerializer, NoteSerializer, ShowSerializer, ScheduleSerializer, CategorySerializer, RTRCategorySerializer, TopicSerializer, TimeSlotSerializer, HostSerializer, UserSerializer
 from program.utils import tofirstdayinisoweek, get_cached_shows
 
@@ -530,7 +532,7 @@ class APIShowViewSet(viewsets.ModelViewSet):
             return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         show = get_object_or_404(Show, pk=pk)
-        Show.objects.delete(pk=pk)
+        Show.objects.get(pk=pk).delete()
 
         return Response(status=status.HTTP_204_NO_CONTENT)
 
@@ -622,7 +624,7 @@ class APIScheduleViewSet(viewsets.ModelViewSet):
             return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         schedule = get_object_or_404(Schedule, pk=pk)
-        Schedule.objects.delete(pk=pk)
+        Schedule.objects.get(pk=pk).delete()
 
         return Response(status=status.HTTP_204_NO_CONTENT)
 
@@ -722,17 +724,21 @@ class APITimeSlotViewSet(viewsets.ModelViewSet):
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
 
-    def destroy(self, request, pk=None):
+    def destroy(self, request, pk=None, schedule_pk=None, show_pk=None):
         """
         Delete a timeslot
         Only superusers may delete timeslots
         """
 
+        # Only allow when calling endpoint starting with /shows/1/...
+        if show_pk == None:
+            return Response(status=status.HTTP_400_BAD_REQUEST)
+
         if not request.user.is_superuser:
             return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         timeslot = get_object_or_404(TimeSlot, pk=pk)
-        TimeSlot.objects.delete(pk=pk)
+        TimeSlot.objects.get(pk=pk).delete()
 
         return Response(status=status.HTTP_204_NO_CONTENT)
 
@@ -803,8 +809,13 @@ class APINoteViewSet(viewsets.ModelViewSet):
             notes = notes.filter(host=int(self.request.GET.get('host')))
 
         if self.request.GET.get('owner') != None:
+            '''Filter notes by show owner: all notes the user may edit'''
+            shows = Show.objects.filter(owners=int(self.request.GET.get('owner')))
+            notes = notes.filter(show__in=shows)
+
+        if self.request.GET.get('user') != None:
             '''Filter notes by their creator'''
-            notes = notes.filter(user=int(self.request.GET.get('owner')))
+            notes = notes.filter(user=int(self.request.GET.get('user')))
 
         return notes
 
@@ -911,7 +922,7 @@ class APINoteViewSet(viewsets.ModelViewSet):
         note = get_object_or_404(Note, pk=pk)
 
         if Note.is_editable(self, note.id):
-            Note.objects.delete(pk=pk)
+            Note.objects.get(pk=pk).delete()
             return Response(status=status.HTTP_204_NO_CONTENT)
 
         return Response(status=status.HTTP_401_UNAUTHORIZED)