diff --git a/program/admin.py b/program/admin.py
index bf09bea59495a50a9a71eb099fc550050e3bc6d9..152fb677dd5b017ee5f48470bf00e9cea9c202ca 100644
--- a/program/admin.py
+++ b/program/admin.py
@@ -84,11 +84,18 @@ class HostAdmin(admin.ModelAdmin):
     list_display = ('name','email',)
     list_filter = (ActiveHostsFilter, 'is_always_visible',)
 
+    def get_queryset(self, request):
+        if request.user.is_superuser:
+            return Host.objects.all()
+
+        # Common users only see hosts of shows they own
+        return Host.objects.filter(shows__in=request.user.shows.all()).distinct()
+
 
 class NoteAdmin(admin.ModelAdmin):
     date_hierarchy = 'start'
     list_display = ('title', 'show', 'start', 'status', 'user')
-    fields = (( 'show', 'timeslot'), 'title', 'slug', 'summary', 'content', 'image', 'status', 'cba_id')
+    fields = (( 'show', 'timeslot'), 'title', 'slug', 'summary', 'content', 'image', 'host', 'status', 'cba_id')
     prepopulated_fields = {'slug': ('title',)}
     list_filter = ('status',)
     ordering = ('timeslot',)
@@ -98,16 +105,18 @@ class NoteAdmin(admin.ModelAdmin):
         js = [ settings.MEDIA_URL + 'js/calendar/lib/moment.min.js',
                settings.MEDIA_URL + 'js/note_change.js', ]
 
+
     def get_queryset(self, request):
         if request.user.is_superuser:
-            # Superusers see notes of all shows
             shows = Show.objects.all()
         else:
-            # Users only see notes of shows they own
+            # Commons users only see notes of shows they own
             shows = request.user.shows.all()
 
         return super(NoteAdmin, self).get_queryset(request).filter(show__in=shows)
 
+
+
     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
         four_weeks_ago = datetime.now() - timedelta(weeks=4)
         in_twelve_weeks = datetime.now() + timedelta(weeks=12)
@@ -134,12 +143,19 @@ class NoteAdmin(admin.ModelAdmin):
         if db_field.name == 'show':
             # Adding/Editing a note: load user's shows into the dropdown
 
-            # Superusers see all shows
+            # Common users only see shows they own
             if not request.user.is_superuser:
                 kwargs['queryset'] = Show.objects.filter(pk__in=request.user.shows.all())
 
+
+        if db_field.name == 'host':
+            # Common users only see hosts of shows they own
+            if not request.user.is_superuser:
+                kwargs['queryset'] = Host.objects.filter(shows__in=request.user.shows.all()).distinct()
+
         return super(NoteAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
+
     def save_model(self, request, obj, form, change):
 
         # Save the creator when adding a note
@@ -227,8 +243,11 @@ class ShowAdmin(admin.ModelAdmin):
 
     def get_readonly_fields(self, request, obj=None):
         '''Limit field access for common users'''
+
         if not request.user.is_superuser:
+            # TODO: how to set field 'name' readonly although it's required?
             return ('predecessor', 'type', 'hosts', 'owners', 'language', 'category', 'topic', 'musicfocus', 'rtrcategory')
+
         return list()
 
 
diff --git a/program/fixtures/group_permissions.yaml b/program/fixtures/group_permissions.yaml
index 20fa7567d6c35ae28963b3eb5cedc31593bf69a2..95191a376e7c0a0ae4d164f3f7a9feb4c6b0f5f5 100644
--- a/program/fixtures/group_permissions.yaml
+++ b/program/fixtures/group_permissions.yaml
@@ -33,4 +33,10 @@
   fields:
     id: 6
     group_id: 1
-    permission_id: 68
\ No newline at end of file
+    permission_id: 68
+- model: auth.group_permissions
+  pk: 7
+  fields:
+    id: 6
+    group_id: 1
+    permission_id: 23
\ No newline at end of file
diff --git a/program/migrations/0012_auto_20171129_1828.py b/program/migrations/0012_auto_20180103_0054.py
similarity index 80%
rename from program/migrations/0012_auto_20171129_1828.py
rename to program/migrations/0012_auto_20180103_0054.py
index b9e2d992eb496711873a14b358dde40e4cabd354..8ba4be8ef317ef9547fe4d3bc8f06b74d2361dc9 100644
--- a/program/migrations/0012_auto_20171129_1828.py
+++ b/program/migrations/0012_auto_20180103_0054.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-11-29 18:28
+# Generated by Django 1.11.3 on 2018-01-02 23:54
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -24,6 +24,8 @@ class Migration(migrations.Migration):
                 ('category', models.CharField(max_length=32, verbose_name='Category')),
                 ('abbrev', models.CharField(max_length=4, unique=True, verbose_name='Abbreviation')),
                 ('slug', models.SlugField(max_length=32, unique=True, verbose_name='Slug')),
+                ('color', models.TextField(blank=True, max_length=7, verbose_name='Color')),
+                ('description', models.TextField(blank=True, verbose_name='Description')),
                 ('button', models.ImageField(blank=True, null=True, upload_to='buttons', verbose_name='Button image')),
                 ('button_hover', models.ImageField(blank=True, null=True, upload_to='buttons', verbose_name='Button image (hover)')),
                 ('big_button', models.ImageField(blank=True, null=True, upload_to='buttons', verbose_name='Big button image')),
@@ -70,7 +72,7 @@ class Migration(migrations.Migration):
                 ('tend', models.TimeField(verbose_name='End time')),
                 ('until', models.DateField(verbose_name='Last date')),
                 ('is_repetition', models.BooleanField(default=False, verbose_name='Is repetition')),
-                ('fallback_playlist_id', models.IntegerField(blank=True, null=True, verbose_name='Fallback Playlist ID')),
+                ('fallback_id', models.IntegerField(blank=True, null=True, verbose_name='Fallback ID')),
                 ('automation_id', models.IntegerField(blank=True, null=True, verbose_name='Automation ID')),
                 ('created', models.DateTimeField(auto_now_add=True, null=True)),
                 ('last_updated', models.DateTimeField(auto_now=True, null=True)),
@@ -150,6 +152,71 @@ class Migration(migrations.Migration):
             model_name='timeslot',
             name='programslot',
         ),
+        migrations.AddField(
+            model_name='host',
+            name='biography',
+            field=tinymce.models.HTMLField(blank=True, help_text='Describe yourself and your fields of interest in a few sentences.', null=True, verbose_name='Biography'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='cba_url',
+            field=models.URLField(blank=True, help_text='URL to your CBA profile.', verbose_name='CBA URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='dorftv_url',
+            field=models.URLField(blank=True, help_text='URL to your dorfTV channel.', verbose_name='DorfTV URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='facebook_url',
+            field=models.URLField(blank=True, help_text='URL to your Facebook profile.', verbose_name='Facebook URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='googleplus_url',
+            field=models.URLField(blank=True, help_text='URL to your Google+ profile.', verbose_name='Google+ URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='height',
+            field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='image',
+            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', help_text="Upload a picture of yourself. Images are automatically cropped around the 'Primary Point of Interest'. Click in the image to change it and press Save.", null=True, upload_to='host_images', verbose_name='Profile picture', width_field='width'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='linkedin_url',
+            field=models.URLField(blank=True, help_text='URL to your LinkedIn profile.', verbose_name='LinkedIn URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='ppoi',
+            field=versatileimagefield.fields.PPOIField(default='0.5x0.5', editable=False, max_length=20, verbose_name='Image PPOI'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='twitter_url',
+            field=models.URLField(blank=True, help_text='URL to your Twitter profile.', verbose_name='Twitter URL'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='width',
+            field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Width'),
+        ),
+        migrations.AddField(
+            model_name='host',
+            name='youtube_url',
+            field=models.URLField(blank=True, help_text='URL to your Youtube channel.', verbose_name='Youtube URL'),
+        ),
+        migrations.AddField(
+            model_name='note',
+            name='audio_url',
+            field=models.TextField(blank=True, editable=False, verbose_name='Direct URL to a linked audio file'),
+        ),
         migrations.AddField(
             model_name='note',
             name='cba_id',
@@ -160,6 +227,11 @@ class Migration(migrations.Migration):
             name='height',
             field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height'),
         ),
+        migrations.AddField(
+            model_name='note',
+            name='host',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hosts', to='program.Host'),
+        ),
         migrations.AddField(
             model_name='note',
             name='image',
@@ -198,8 +270,8 @@ class Migration(migrations.Migration):
         ),
         migrations.AddField(
             model_name='show',
-            name='fallback_pool',
-            field=models.CharField(blank=True, max_length=255, verbose_name='Fallback Pool'),
+            name='fallback_id',
+            field=models.IntegerField(blank=True, null=True, verbose_name='Fallback ID'),
         ),
         migrations.AddField(
             model_name='show',
@@ -224,7 +296,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='timeslot',
             name='is_repetition',
-            field=models.BooleanField(default=False, verbose_name='WH'),
+            field=models.BooleanField(default=False, verbose_name='REP'),
         ),
         migrations.AddField(
             model_name='timeslot',
@@ -236,6 +308,11 @@ class Migration(migrations.Migration):
             name='playlist_id',
             field=models.IntegerField(null=True, verbose_name='Playlist ID'),
         ),
+        migrations.AlterField(
+            model_name='host',
+            name='website',
+            field=models.URLField(blank=True, help_text='URL to your personal website.', verbose_name='Website'),
+        ),
         migrations.AlterField(
             model_name='musicfocus',
             name='big_button',
diff --git a/program/migrations/0013_category_color.py b/program/migrations/0013_category_color.py
deleted file mode 100644
index 1dcdab40ab9dcb37db14ee34a41a89c006817e82..0000000000000000000000000000000000000000
--- a/program/migrations/0013_category_color.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-12-12 15:14
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('program', '0012_auto_20171129_1828'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='category',
-            name='color',
-            field=models.TextField(blank=True, max_length=7, verbose_name='Color'),
-        ),
-    ]
diff --git a/program/migrations/0014_category_description.py b/program/migrations/0014_category_description.py
deleted file mode 100644
index 31bf87bc7ecf1ab5358a5101f39a55f15d229b50..0000000000000000000000000000000000000000
--- a/program/migrations/0014_category_description.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-12-12 15:18
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('program', '0013_category_color'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='category',
-            name='description',
-            field=models.TextField(blank=True, verbose_name='Description'),
-        ),
-    ]
diff --git a/program/migrations/0015_note_audio_url.py b/program/migrations/0015_note_audio_url.py
deleted file mode 100644
index 5a107364a0b1dcbcabbb36f01940cd0a33b54990..0000000000000000000000000000000000000000
--- a/program/migrations/0015_note_audio_url.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-12-12 19:18
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('program', '0014_category_description'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='note',
-            name='audio_url',
-            field=models.TextField(blank=True, editable=False, verbose_name='Direct URL to a linked audio file'),
-        ),
-    ]
diff --git a/program/migrations/0016_auto_20171213_1737.py b/program/migrations/0016_auto_20171213_1737.py
deleted file mode 100644
index c87b746ced70f2a609271de34e125fd891ebd6d5..0000000000000000000000000000000000000000
--- a/program/migrations/0016_auto_20171213_1737.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-12-13 17:37
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import tinymce.models
-import versatileimagefield.fields
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('program', '0015_note_audio_url'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='host',
-            name='biography',
-            field=tinymce.models.HTMLField(blank=True, help_text='Describe yourself and your fields of interest in a few sentences.', null=True, verbose_name='Biography'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='cba_url',
-            field=models.URLField(blank=True, help_text='URL to your CBA profile.', verbose_name='CBA URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='dorftv_url',
-            field=models.URLField(blank=True, help_text='URL to your dorfTV channel.', verbose_name='DorfTV URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='facebook_url',
-            field=models.URLField(blank=True, help_text='URL to your Facebook profile.', verbose_name='Facebook URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='googleplus_url',
-            field=models.URLField(blank=True, help_text='URL to your Google+ profile.', verbose_name='Google+ URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='height',
-            field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='image',
-            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', help_text="Upload a picture of yourself. Images are automatically cropped around the 'Primary Point of Interest'. Click in the image to change it and press Save.", null=True, upload_to='user_images', verbose_name='Profile picture', width_field='width'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='linkedin_url',
-            field=models.URLField(blank=True, help_text='URL to your LinkedIn profile.', verbose_name='LinkedIn URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='ppoi',
-            field=versatileimagefield.fields.PPOIField(default='0.5x0.5', editable=False, max_length=20, verbose_name='Image PPOI'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='twitter_url',
-            field=models.URLField(blank=True, help_text='URL to your Twitter profile.', verbose_name='Twitter URL'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='width',
-            field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Width'),
-        ),
-        migrations.AddField(
-            model_name='host',
-            name='youtube_url',
-            field=models.URLField(blank=True, help_text='URL to your Youtube channel.', verbose_name='Youtube URL'),
-        ),
-        migrations.AlterField(
-            model_name='host',
-            name='website',
-            field=models.URLField(blank=True, help_text='URL to your personal website.', verbose_name='Website'),
-        ),
-        migrations.AlterField(
-            model_name='timeslot',
-            name='is_repetition',
-            field=models.BooleanField(default=False, verbose_name='REP'),
-        ),
-    ]
diff --git a/program/migrations/0017_auto_20180102_1535.py b/program/migrations/0017_auto_20180102_1535.py
deleted file mode 100644
index bc5a82cbb6c6b7754a3e5f87317d1515fb507f03..0000000000000000000000000000000000000000
--- a/program/migrations/0017_auto_20180102_1535.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2018-01-02 15:35
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import versatileimagefield.fields
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('program', '0016_auto_20171213_1737'),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name='schedule',
-            name='fallback_playlist_id',
-        ),
-        migrations.RemoveField(
-            model_name='show',
-            name='fallback_pool',
-        ),
-        migrations.AddField(
-            model_name='schedule',
-            name='fallback_id',
-            field=models.IntegerField(blank=True, null=True, verbose_name='Fallback ID'),
-        ),
-        migrations.AddField(
-            model_name='show',
-            name='fallback_id',
-            field=models.CharField(blank=True, max_length=255, verbose_name='Fallback ID'),
-        ),
-        migrations.AlterField(
-            model_name='host',
-            name='image',
-            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', help_text="Upload a picture of yourself. Images are automatically cropped around the 'Primary Point of Interest'. Click in the image to change it and press Save.", null=True, upload_to='host_images', verbose_name='Profile picture', width_field='width'),
-        ),
-    ]
diff --git a/program/models.py b/program/models.py
index 21ec31c55507ff858bd316afcaeedd35da2586da..468f88cb7d70880d23cb8796bc860cc72cf10ce4 100644
--- a/program/models.py
+++ b/program/models.py
@@ -271,6 +271,17 @@ class Host(models.Model):
     def active_shows(self):
         return self.shows.filter(schedules__until__gt=datetime.today())
 
+    def is_editable(self, host_id):
+        """
+        Whether the given host is assigned to a show the current user owns
+        @return boolean
+        """
+        if self.request.user.is_superuser:
+            return True
+
+        host_ids = Host.objects.filter(shows__in=self.request.user.shows.all()).distinct().values_list('id', flat=True)
+        return int(host_id) in host_ids
+
     def save(self, *args, **kwargs):
         super(Host, self).save(*args, **kwargs)
 
@@ -304,7 +315,7 @@ class Show(models.Model):
     email = models.EmailField(_("E-Mail"), blank=True, null=True, help_text=_("The main contact email address for your show."))
     website = models.URLField(_("Website"), blank=True, null=True, help_text=_("Is there a website to your show? Type in its URL."))
     cba_series_id = models.IntegerField(_("CBA Series ID"), blank=True, null=True, help_text=_("Link your show to a CBA series by giving its ID. This will enable CBA upload and will automatically link your show to your CBA archive. Find out your ID under https://cba.fro.at/series"))
-    fallback_id = models.CharField(_("Fallback ID"), max_length=255, blank=True)
+    fallback_id = models.IntegerField(_("Fallback ID"), blank=True, null=True)
     created = models.DateTimeField(auto_now_add=True, editable=False)
     last_updated = models.DateTimeField(auto_now=True, editable=False)
 
@@ -328,14 +339,14 @@ class Show(models.Model):
 
     def is_editable(self, show_id):
         """
-        Whether the current user can edit the given show
+        Whether the current user is owner of the given show
         @return boolean
         """
         if self.request.user.is_superuser:
             return True
-        else:
-            show_ids = self.request.user.shows.all().values_list('id', flat=True)
-            return int(show_id) in show_ids
+
+        show_ids = self.request.user.shows.all().values_list('id', flat=True)
+        return int(show_id) in show_ids
 
 
 class RRule(models.Model):
@@ -596,6 +607,12 @@ class TimeSlotManager(models.Manager):
                                        Q(start__gt=start, start__lt=end)).exclude(end=start)
 
 
+    @staticmethod
+    def get_timerange_timeslots(start, end):
+        return TimeSlot.objects.filter(Q(start__lte=start, end__gte=start) |
+                                       Q(start__gt=start, start__lt=end)).exclude(end=start)
+
+
 class TimeSlot(models.Model):
     schedule = models.ForeignKey(Schedule, related_name='timeslots', verbose_name=_("Schedule"))
     start = models.DateTimeField(_("Start time")) # Removed 'unique=True' because new Timeslots need to be created before deleting the old ones (otherwise linked notes get deleted first)
@@ -656,6 +673,7 @@ class Note(models.Model):
     created = models.DateTimeField(auto_now_add=True, editable=False)
     last_updated = models.DateTimeField(auto_now=True, editable=False)
     user = models.ForeignKey(User, editable=False, related_name='users', default=1)
+    host = models.ForeignKey(Host, related_name='hosts', null=True)
 
     class Meta:
         ordering = ('timeslot',)
@@ -665,6 +683,16 @@ class Note(models.Model):
     def __str__(self):
         return '%s - %s' % (self.title, self.timeslot)
 
+    def is_editable(self, note_id):
+        """
+        Whether the given note is assigned to a show the current user owns
+        @return boolean
+        """
+        if self.request.user.is_superuser:
+            return True
+
+        return int(note_id) in self.request.user.shows.all().values_list('id', flat=True)
+
     def get_audio_url(cba_id):
         """
         Retrieve the direct URL to the mp3 in CBA
@@ -679,7 +707,7 @@ class Note(models.Model):
 
         audio_url = ''
 
-        if cba_id != '' and CBA_API_KEY != '':
+        if cba_id != None and cba_id != '' and CBA_API_KEY != '':
             from urllib.request import urlopen
             import json
 
diff --git a/program/serializers.py b/program/serializers.py
index fb950785f455103f1d79b3818e48a62e42951650..a1b6936b18455fada66d5018a27eacb4d472fb65 100644
--- a/program/serializers.py
+++ b/program/serializers.py
@@ -28,7 +28,7 @@ class UserSerializer(serializers.ModelSerializer):
         user.set_password(validated_data['password'])
         user.save()
 
-        profile = Profile(user=user, cba_username=profile_data.get('cba_username'), cba_user_token=profile_data.get('cba_user_token'))
+        profile = Profile(user=user, cba_username=profile_data.get('cba_username').strip(), cba_user_token=profile_data.get('cba_user_token').strip())
         profile.save()
 
         return user
@@ -98,7 +98,6 @@ class HostSerializer(serializers.ModelSerializer):
         Update and return an existing Host instance, given the validated data.
         """
 
-        # TODO: Still put this into a sub app?
         instance.name = validated_data.get('name', instance.name)
         instance.is_always_visible = validated_data.get('is_always_visible', instance.is_always_visible)
         instance.email = validated_data.get('email', instance.email)
@@ -207,14 +206,6 @@ class RTRCategorySerializer(serializers.ModelSerializer):
         return instance
 
 
-'''
-class OwnersSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = Owners
-        fields = '__all__'
-'''
-
-
 class ShowSerializer(serializers.HyperlinkedModelSerializer):
     owners = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(),many=True)
     category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(),many=True)
@@ -360,11 +351,13 @@ class TimeSlotSerializer(serializers.ModelSerializer):
 class NoteSerializer(serializers.ModelSerializer):
     show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())
     timeslot = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all())
+    host = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all())
 
     class Meta:
         model = Note
         fields = '__all__'
 
+
     def create(self, validated_data):
         """Create and return a new Note instance, given the validated data."""
 
@@ -388,6 +381,7 @@ class NoteSerializer(serializers.ModelSerializer):
         instance.content = validated_data.get('content', instance.content)
         instance.image = validated_data.get('image', instance.image)
         instance.status = validated_data.get('status', instance.status)
+        instance.host = validated_data.get('host', instance.host)
         instance.cba_id = validated_data.get('cba_id', instance.cba_id)
         instance.audio_url = Note.get_audio_url(instance.cba_id)
 
diff --git a/program/views.py b/program/views.py
index 63754bc70f3bc552acb995808a13e905436d50aa..dfe7ee74bf1268a8e9f4f96d60113666164a9d30 100644
--- a/program/views.py
+++ b/program/views.py
@@ -228,11 +228,16 @@ def json_day_schedule(request, year=None, month=None, day=None):
                         content_type="application/json; charset=utf-8")
 
 
-def json_week_schedule(request):
+def json_playout(request):
     """
-    Called by calendar to get all timeslots for a week.
-    Expects GET variable 'start' (date), otherwise start will be today
-    Returns all timeslots of the next 7 days
+    Called by
+       - engine (playout) to retrieve timeslots within a given timerange
+         Expects GET variables 'start' (date) and 'end' (date).
+         If start not given, it will be today
+
+       - internal calendar to retrieve all timeslots for a week
+         Expects GET variable 'start' (date), otherwise start will be today
+         If end not given, it returns all timeslots of the next 7 days
     """
 
     if request.GET.get('start') == None:
@@ -240,7 +245,14 @@ def json_week_schedule(request):
     else:
         start = datetime.combine( datetime.strptime(request.GET.get('start'), '%Y-%m-%d').date(), time(0, 0))
 
-    timeslots = TimeSlot.objects.get_7d_timeslots(start).select_related('schedule').select_related('show')
+    if request.GET.get('end') == None:
+        # If no end was given, return the next week
+        timeslots = TimeSlot.objects.get_7d_timeslots(start).select_related('schedule').select_related('show')
+    else:
+        # Otherwise return the given timerange
+        end = datetime.combine( datetime.strptime(request.GET.get('end'), '%Y-%m-%d').date(), time(23, 59))
+        timeslots = TimeSlot.objects.get_timerange_timeslots(start, end).select_related('schedule').select_related('show')
+
     schedule = []
     for ts in timeslots:
 
@@ -268,8 +280,8 @@ def json_week_schedule(request):
             'schedule_id': ts.schedule.id,
             'is_repetition': ts.is_repetition,
             'playlist_id': ts.playlist_id,
-            'schedule_fallback_id': ts.schedule.fallback_playlist_id, # The schedule's fallback
-            'show_fallback_id': ts.show.fallback_pool, # The show's fallback
+            'schedule_fallback_id': ts.schedule.fallback_id, # The schedule's fallback
+            'show_fallback_id': ts.show.fallback_id, # The show's fallback
             'show_id': ts.show.id,
             'show_name': ts.show.name + is_repetition,
             'show_hosts': hosts,
@@ -344,8 +356,8 @@ class APIUserViewSet(viewsets.ModelViewSet):
         """Constrain access to oneself except for superusers"""
         if self.request.user.is_superuser:
             return User.objects.all()
-        else:
-            return User.objects.filter(pk=self.request.user.id)
+
+        return User.objects.filter(pk=self.request.user.id)
 
 
     def list(self, request):
@@ -409,7 +421,9 @@ class APIUserViewSet(viewsets.ModelViewSet):
 class APIShowViewSet(viewsets.ModelViewSet):
     """
     /api/v1/shows/                                             Returns shows a user owns (GET, POST)
-    /api/v1/shows/?active=true                                 Returns all active shows (no matter if owned by user) (GET)
+    /api/v1/shows/?active=true                                 Returns all active shows (GET)
+    /api/v1/shows/?host=1                                      Returns shows assigned to a given host (GET)
+    /api/v1/shows/?owner=1                                     Returns shows of a given owner (GET)
     /api/v1/shows/1                                            Used for retrieving a single show or update (if owned) (GET, PUT, DELETE)
     /api/v1/shows/1/notes                                      Returns all notes to the show (GET) - POST not allowed at this level, use /shows/1/schedules/1/timeslots/1/note instead
     /api/v1/shows/1/notes/1                                    Returns the note of the show by its ID (GET) - PUT/DELETE not allowed at this level, use /shows/1/schedules/1/timeslots/1/note/1/ instead
@@ -433,10 +447,12 @@ class APIShowViewSet(viewsets.ModelViewSet):
 
     def get_queryset(self):
 
+        shows = Show.objects.all()
+
         if self.request.GET.get('active') == 'true':
-            '''Filter for retrieving currently running shows'''
+            '''Filter currently running shows'''
 
-            # Get currently running schedules to filter by
+            # Get currently running schedules to filter by first
             # For single dates we test if there'll be one in the future (and ignore the until date)
             # TODO: Really consider dstart? (=currently active, not just upcoming ones)
             # Add limit for future?
@@ -444,17 +460,18 @@ class APIShowViewSet(viewsets.ModelViewSet):
                                                  Q(rrule_id=1,dstart__gte=date.today())
                                                ).distinct().values_list('show_id', flat=True)
 
-            return Show.objects.filter(id__in=schedules)
+            shows = Show.objects.filter(id__in=schedules)
 
-        return Show.objects.all()
+        if self.request.GET.get('owner') != None:
+            '''Filter shows by owner'''
+            shows = shows.filter(owners__in=[int(self.request.GET.get('owner'))])
+
+        if self.request.GET.get('host') != None:
+            '''Filter shows by host'''
+            shows = shows.filter(hosts__in=[int(self.request.GET.get('host'))])
+
+        return shows
 
-    '''
-    def list(self, request):
-        """List shows"""
-        shows = self.get_queryset()
-        serializer = ShowSerializer(shows, many=True)
-        return Response(serializer.data)
-    '''
 
     def create(self, request, pk=None):
         """
@@ -494,6 +511,9 @@ class APIShowViewSet(viewsets.ModelViewSet):
         serializer = ShowSerializer(show, data=request.data, context={ 'user': request.user })
 
         if serializer.is_valid():
+            # Common users mustn't edit the show's name
+            if not request.user.is_superuser:
+                serializer.validated_data['name'] = show.name
             serializer.save();
             return Response(serializer.data)
 
@@ -567,7 +587,7 @@ class APIScheduleViewSet(viewsets.ModelViewSet):
 
         # Only allow creating when calling /shows/1/schedules/1
         if show_pk == None or not request.user.is_superuser:
-            return Response(status=HTTP_401_UNAUTHORIZED)
+            return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         return Response(status=HTTP_401_UNAUTHORIZED)
 
@@ -642,24 +662,30 @@ class APITimeSlotViewSet(viewsets.ModelViewSet):
 
         # If show is given: Return corresponding timeslots
         if show_pk != None and schedule_pk != None:
-            # /shows/1/schedules/1/timeslots/ returns timeslots of the given schedule and show
+            #
+            #     /shows/1/schedules/1/timeslots/
+            #
+            #     Returns timeslots of the given show and schedule
+            #
             return TimeSlot.objects.filter(show=show_pk, schedule=schedule_pk, start__gte=start, end__lte=end).order_by('start')
+
         elif show_pk != None and schedule_pk == None:
-            # /shows/1/timeslots returns timeslots of the show
+            #
+            #     /shows/1/timeslots/
+            #
+            #     Returns timeslots of the show
+            #
             return TimeSlot.objects.filter(show=show_pk, start__gte=start, end__lte=end).order_by('start')
+
         else:
-            # Otherwise return all timeslots
+            #
+            #     /timeslots/
+            #
+            #     Returns all timeslots
+            #
             return TimeSlot.objects.filter(start__gte=start, end__lte=end).order_by('start')
 
 
-    '''
-    def list(self, request, show_pk=None, schedule_pk=None):
-        """Lists timeslots of a show"""
-        timeslots = self.get_queryset()
-        serializer = TimeSlotSerializer(timeslots, many=True)
-        return Response(serializer.data)
-    '''
-
     def retrieve(self, request, pk=None, schedule_pk=None, show_pk=None):
 
         if show_pk != None:
@@ -685,7 +711,7 @@ class APITimeSlotViewSet(viewsets.ModelViewSet):
         timeslot = get_object_or_404(TimeSlot, pk=pk, schedule=schedule_pk, show=show_pk)
 
         # Update is only allowed when calling /shows/1/schedules/1/timeslots/1 and if user owns the show
-        if schedule_pk == None or show_pk==None or not Show.is_editable(self, timeslot.show_id):
+        if schedule_pk == None or show_pk == None or not Show.is_editable(self, timeslot.show_id):
             return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         serializer = TimeSlotSerializer(timeslot, data=request.data)
@@ -717,6 +743,8 @@ class APINoteViewSet(viewsets.ModelViewSet):
     /api/v1/notes/                                  Returns all notes (GET) - POST not allowed at this level
     /ap1/v1/notes/1                                 Returns a single note (if owned) (GET) - PUT/DELETE not allowed at this level
     /api/v1/notes/?ids=1,2,3,4,5                    Returns given notes (if owned) (GET)
+    /api/v1/notes/?host=1                           Returns notes assigned to a given host (GET)
+    /api/v1/notes/?owner=1                          Returns notes created by a given user (GET)
     /api/v1/shows/1/notes                           Returns all notes of a show (GET) - POST not allowed at this level
     /api/v1/shows/1/notes/1                         Returns a note by its ID (GET) - PUT/DELETE not allowed at this level
     /api/v1/shows/1/timeslots/1/note/               Returns a note of the timeslot (GET) - POST not allowed at this level
@@ -740,28 +768,43 @@ class APINoteViewSet(viewsets.ModelViewSet):
         timeslot_pk = self.kwargs['timeslot_pk'] if 'timeslot_pk' in self.kwargs else None
         show_pk = self.kwargs['show_pk'] if 'show_pk' in self.kwargs else None
 
-        # TODO: Should users be able to edit other's notes if they're part of a show they own?
+        if show_pk != None and timeslot_pk != None:
+            #
+            #     /shows/1/schedules/1/timeslots/1/note
+            #     /shows/1/timeslots/1/note
+            #
+            #     Return a note to the timeslot
+            #
+            notes = Note.objects.filter(show=show_pk, timeslot=timeslot_pk)
+
+        elif show_pk != None and timeslot_pk == None:
+            #
+            #     /shows/1/notes
+            #
+            #     Returns notes to the show
+            #
+            notes = Note.objects.filter(show=show_pk)
+
+        else:
+            #
+            #     /notes
+            #
+            #     Returns all notes
+            #
+            notes = Note.objects.all()
+
         if self.request.GET.get('ids') != None:
+            '''Filter notes by their IDs'''
             note_ids = self.request.GET.get('ids').split(',')
-            if self.request.user.is_superuser:
-                notes = Note.objects.filter(id__in=note_ids)
-            else:
-                # Common users only retrieve notes they own
-                notes = Note.objects.filter(id__in=note_ids,user=self.request.user.id)
-        else:
+            notes = notes.filter(id__in=note_ids)
+
+        if self.request.GET.get('host') != None:
+            '''Filter notes by host'''
+            notes = notes.filter(host=int(self.request.GET.get('host')))
 
-            if show_pk != None and timeslot_pk != None:
-                # /shows/1/schedules/1/timeslots/1/note
-                # /shows/1/timeslots/1/note/
-                # ...return notes to the timeslot
-                notes = Note.objects.filter(show=show_pk, timeslot=timeslot_pk)
-            elif show_pk != None and timeslot_pk == None:
-                # /shows/1/notes returns notes to the show
-                notes = Note.objects.filter(show=show_pk)
-            else:
-                # /notes returns all notes
-                notes = Note.objects.all()
-                #notes = Note.objects.filter(user=self.request.user.id)
+        if self.request.GET.get('owner') != None:
+            '''Filter notes by their creator'''
+            notes = notes.filter(user=int(self.request.GET.get('owner')))
 
         return notes
 
@@ -787,6 +830,11 @@ class APINoteViewSet(viewsets.ModelViewSet):
         serializer = NoteSerializer(data=request.data, context={ 'user_id': request.user.id })
 
         if serializer.is_valid():
+
+            # Don't assign a host the user mustn't edit
+            if not Host.is_editable(self, request.data['host']) or request.data['host'] == None:
+                serializer.validated_data['host'] = None
+
             serializer.save()
             return Response(serializer.data)
 
@@ -805,14 +853,28 @@ class APINoteViewSet(viewsets.ModelViewSet):
         """
 
         if show_pk != None and timeslot_pk == None and schedule_pk == None:
-            # /shows/1/notes/1
+            #
+            #      /shows/1/notes/1
+            #
+            #      Returns a note to a show
+            #
             note = get_object_or_404(Note, pk=pk, show=show_pk)
+
         elif show_pk != None and timeslot_pk != None:
-            # /shows/1/timeslots/1/note/1
-            # /shows/1/schedules/1/timeslots/1/note/1
+            #
+            #     /shows/1/timeslots/1/note/1
+            #     /shows/1/schedules/1/timeslots/1/note/1
+            #
+            #     Return a note to a timeslot
+            #
             note = get_object_or_404(Note, pk=pk, show=show_pk, timeslot=timeslot_pk)
+
         else:
-            # /notes/1
+            #
+            #     /notes/1
+            #
+            #     Returns the given note
+            #
             note = get_object_or_404(Note, pk=pk)
 
         serializer = NoteSerializer(note)
@@ -823,17 +885,22 @@ class APINoteViewSet(viewsets.ModelViewSet):
 
         # Allow PUT only when calling /shows/1/schedules/1/timeslots/1/note/1
         if show_pk == None or schedule_pk == None or timeslot_pk == None:
-            return Response(status=status.HTTP_401_UNAUTHORIZED)
+            return Response(status=status.HTTP_400_BAD_REQUEST)
 
         note = get_object_or_404(Note, pk=pk, timeslot=timeslot_pk, show=show_pk)
 
-        # Commons users may only edit their own notes
-        if not request.user.is_superuser and note.user_id != request.user.id:
+        # Commons users may only edit notes of shows they own
+        if not Note.is_editable(self, note_id):
             return Response(status=status.HTTP_401_UNAUTHORIZED)
 
         serializer = NoteSerializer(note, data=request.data)
 
         if serializer.is_valid():
+
+            # Don't assign a host the user mustn't edit. Reassign the original value instead
+            if not Host.is_editable(self, request.data['host']) and request.data['host'] != None:
+                serializer.validated_data['host'] = Host.objects.filter(pk=note.host_id)[0]
+
             serializer.save();
             return Response(serializer.data)
 
@@ -843,13 +910,11 @@ class APINoteViewSet(viewsets.ModelViewSet):
     def destroy(self, request, pk=None):
         note = get_object_or_404(Note, pk=pk)
 
-        # Commons users may only delete their own notes
-        if not request.user.is_superuser and note.user_id != request.user.id:
-            return Response(status=status.HTTP_401_UNAUTHORIZED)
-
-        Note.objects.delete(pk=pk)
-        return Response(status=status.HTTP_204_NO_CONTENT)
+        if Note.is_editable(self, note.id):
+            Note.objects.delete(pk=pk)
+            return Response(status=status.HTTP_204_NO_CONTENT)
 
+        return Response(status=status.HTTP_401_UNAUTHORIZED)
 
 
 class APICategoryViewSet(viewsets.ModelViewSet):
@@ -937,16 +1002,4 @@ class APIHostViewSet(viewsets.ModelViewSet):
     queryset = Host.objects.all()
     serializer_class = HostSerializer
     permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
-    required_scopes = ['hosts']
-
-
-'''
-class APIOwnersViewSet(viewsets.ModelViewSet):
-    """
-    """
-
-    queryset = Owners.objects.all()
-    serializer_class = OwnersSerializer
-    permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly]
-    required_scopes = ['owners']
-'''
\ No newline at end of file
+    required_scopes = ['hosts']
\ No newline at end of file
diff --git a/pv/settings.py b/pv/settings.py
index e9edf97ed45f8c453bc2b02426015d7a21895f5f..ec41cf7c08ecdbecda1d63f5dd0c57ea50ec5afe 100644
--- a/pv/settings.py
+++ b/pv/settings.py
@@ -8,6 +8,9 @@ PROJECT_DIR = os.path.dirname(__file__)
 
 DEBUG = True
 
+# Must be set if DEBUG is False
+ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
+
 ADMINS = ()
 
 MANAGERS = ADMINS
@@ -27,6 +30,9 @@ DATABASE_ROUTERS = ['nop.dbrouter.NopRouter']
 
 TIME_ZONE = 'Europe/Vienna'
 
+# django-oidc-provider needs timezones in database
+USE_TZ = True
+
 LANGUAGE_CODE = 'de'
 
 SITE_ID = 1
@@ -86,8 +92,6 @@ REST_FRAMEWORK = {
     ],
     'DEFAULT AUTHENTICATION_CLASSES': [
     ],
-    #'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
-    #'PAGE_SIZE': 100
 }
 
 INSTALLED_APPS = (
@@ -106,6 +110,7 @@ INSTALLED_APPS = (
     'rest_framework',
     'rest_framework_nested',
     'frapp',
+    'oidc_provider',
 )
 
 THUMBNAIL_SIZES = ['640x480', '200x200', '150x150']
diff --git a/pv/urls.py b/pv/urls.py
index 0844b7df0d63f0ee84a4130d71cac03ae012e4aa..83285c0098dbf4e419a56bd7f397ab87fe0f9b5f 100644
--- a/pv/urls.py
+++ b/pv/urls.py
@@ -4,8 +4,9 @@ from django.contrib import admin
 from django.views.static import serve
 from rest_framework_nested import routers
 from rest_framework.authtoken import views
+from oidc_provider import urls
 
-from program.views import APIUserViewSet, APIHostViewSet, APIShowViewSet, APIScheduleViewSet, APITimeSlotViewSet, APINoteViewSet, APICategoryViewSet, APITypeViewSet, APITopicViewSet, APIMusicFocusViewSet, APIRTRCategoryViewSet, APILanguageViewSet, json_day_schedule, json_week_schedule, json_timeslots_specials
+from program.views import APIUserViewSet, APIHostViewSet, APIShowViewSet, APIScheduleViewSet, APITimeSlotViewSet, APINoteViewSet, APICategoryViewSet, APITypeViewSet, APITopicViewSet, APIMusicFocusViewSet, APIRTRCategoryViewSet, APILanguageViewSet, json_day_schedule, json_playout, json_timeslots_specials
 
 admin.autodiscover()
 
@@ -54,13 +55,14 @@ timeslot_router.register(r'note', APINoteViewSet, base_name='timeslots-note')
 
 
 urlpatterns = [
+    url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
     url(r'^api/v1/', include(router.urls) ),
     url(r'^api/v1/', include(show_router.urls)),
     url(r'^api/v1/', include(show_timeslot_router.urls)),
     url(r'^api/v1/', include(schedule_router.urls)),
     url(r'^api/v1/', include(timeslot_router.urls)),
-    url(r'^api/v1/playout', json_week_schedule),
-    url(r'^api/v1/program/week', json_week_schedule),
+    url(r'^api/v1/playout', json_playout),
+    url(r'^api/v1/program/week', json_playout),
     url(r'^api/v1/program/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', json_day_schedule),
     url(r'^admin/', admin.site.urls),
     url(r'^program/', include('program.urls')),
diff --git a/requirements.txt b/requirements.txt
index aa962397cfdb466b2608f6983d33b9a6e331c333..207448256a970634a1cc194abd45f825af692cc8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,4 +7,4 @@ python-dateutil==2.6.0
 django-versatileimagefield==1.8.1
 djangorestframework
 drf-nested-routers==0.90.0
-django-oauth-toolkit
\ No newline at end of file
+django-oidc-provider==0.5.2
\ No newline at end of file