diff --git a/profile/migrations/0002_auto_20171129_1828.py b/profile/migrations/0002_auto_20171129_1828.py
new file mode 100644
index 0000000000000000000000000000000000000000..737c88e2a016c6b5f5f4248cd2757fbe2f43c753
--- /dev/null
+++ b/profile/migrations/0002_auto_20171129_1828.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.3 on 2017-11-29 18:28
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import tinymce.models
+import versatileimagefield.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('profile', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='profile',
+            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.AlterField(
+            model_name='profile',
+            name='cba_url',
+            field=models.URLField(blank=True, help_text='URL to your CBA profile.', verbose_name='CBA URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='cba_user_token',
+            field=models.CharField(blank=True, help_text='The CBA upload token for your account. This is NOT your password which you use to log into CBA!', max_length=255, verbose_name='CBA Token'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='cba_username',
+            field=models.CharField(blank=True, help_text='Your username in CBA. This is necessary for uploading files to your account.', max_length=60, verbose_name='CBA Username'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='dorftv_url',
+            field=models.URLField(blank=True, help_text='URL to your dorfTV channel.', verbose_name='DorfTV URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='facebook_url',
+            field=models.URLField(blank=True, help_text='URL to your Facebook profile.', verbose_name='Facebook URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='googleplus_url',
+            field=models.URLField(blank=True, help_text='URL to your Google+ profile.', verbose_name='Google+ URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            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.AlterField(
+            model_name='profile',
+            name='linkedin_url',
+            field=models.URLField(blank=True, help_text='URL to your LinkedIn profile.', verbose_name='LinkedIn URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='twitter_url',
+            field=models.URLField(blank=True, help_text='URL to your Twitter profile.', verbose_name='Twitter URL'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='user',
+            field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='website',
+            field=models.URLField(blank=True, help_text='URL to your personal website.', verbose_name='Website'),
+        ),
+        migrations.AlterField(
+            model_name='profile',
+            name='youtube_url',
+            field=models.URLField(blank=True, help_text='URL to your Youtube channel.', verbose_name='Youtube URL'),
+        ),
+    ]
diff --git a/profile/models.py b/profile/models.py
index a02b556b0fd63ca0346f5bbf800fda9b1ba7fd48..bcb5ebd4b4c84f52fe8b1706c8d808d7c7b749be 100644
--- a/profile/models.py
+++ b/profile/models.py
@@ -11,7 +11,7 @@ import os
 from tinymce import models as tinymce_models
 
 class Profile(models.Model):
-    user = models.OneToOneField(User, on_delete=models.CASCADE)
+    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', editable=False)
     biography = tinymce_models.HTMLField(_("Biography"), blank=True, null=True, help_text=_("Describe yourself and your fields of interest in a few sentences."))
     website = models.URLField(_("Website"), blank=True, help_text=_("URL to your personal website."))
     googleplus_url = models.URLField(_("Google+ URL"), blank=True, help_text=_("URL to your Google+ profile."))
diff --git a/program/migrations/0012_auto_20171122_1718.py b/program/migrations/0012_auto_20171129_1828.py
similarity index 77%
rename from program/migrations/0012_auto_20171122_1718.py
rename to program/migrations/0012_auto_20171129_1828.py
index 3141a578f5162e2d4a1f7f2880dbf6863d15b641..b9e2d992eb496711873a14b358dde40e4cabd354 100644
--- a/program/migrations/0012_auto_20171122_1718.py
+++ b/program/migrations/0012_auto_20171129_1828.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.3 on 2017-11-22 17:18
+# Generated by Django 1.11.3 on 2017-11-29 18:28
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -153,7 +153,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='note',
             name='cba_id',
-            field=models.IntegerField(blank=True, null=True, verbose_name='CBA ID'),
+            field=models.IntegerField(blank=True, help_text="Link the note to a certain CBA post by giving its ID. (E.g. if your post's CBA URL is https://cba.fro.at/1234, then your CBA ID is 1234)", null=True, verbose_name='CBA ID'),
         ),
         migrations.AddField(
             model_name='note',
@@ -163,7 +163,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='note',
             name='image',
-            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', null=True, upload_to='note_images', verbose_name='Featured image', width_field='width'),
+            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', help_text="Upload an image to your show. Images are automatically cropped around the 'Primary Point of Interest'. Click in the image to change it and press Save.", null=True, upload_to='note_images', verbose_name='Featured image', width_field='width'),
         ),
         migrations.AddField(
             model_name='note',
@@ -173,13 +173,13 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='note',
             name='slug',
-            field=models.SlugField(default=1, max_length=32, unique=True, verbose_name='Slug'),
+            field=models.SlugField(default=1, help_text='A simple to read URL for your show.', max_length=32, unique=True, verbose_name='Slug'),
             preserve_default=False,
         ),
         migrations.AddField(
             model_name='note',
             name='summary',
-            field=tinymce.models.HTMLField(blank=True, verbose_name='Summary'),
+            field=models.TextField(blank=True, help_text='Describe your upcoming show in some sentences. Avoid technical data like airing times and contact information. They will be added automatically.', verbose_name='Summary'),
         ),
         migrations.AddField(
             model_name='note',
@@ -194,7 +194,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name='show',
             name='cba_series_id',
-            field=models.IntegerField(blank=True, null=True, verbose_name='CBA Series ID'),
+            field=models.IntegerField(blank=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', null=True, verbose_name='CBA Series ID'),
         ),
         migrations.AddField(
             model_name='show',
@@ -231,6 +231,11 @@ class Migration(migrations.Migration):
             name='memo',
             field=models.TextField(blank=True, verbose_name='Memo'),
         ),
+        migrations.AddField(
+            model_name='timeslot',
+            name='playlist_id',
+            field=models.IntegerField(null=True, verbose_name='Playlist ID'),
+        ),
         migrations.AlterField(
             model_name='musicfocus',
             name='big_button',
@@ -246,15 +251,55 @@ class Migration(migrations.Migration):
             name='button_hover',
             field=models.ImageField(blank=True, null=True, upload_to='buttons', verbose_name='Button image (hover)'),
         ),
+        migrations.AlterField(
+            model_name='note',
+            name='content',
+            field=tinymce.models.HTMLField(help_text='Describe your upcoming show in detail.', verbose_name='Content'),
+        ),
         migrations.AlterField(
             model_name='note',
             name='show',
             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='program.Show'),
         ),
+        migrations.AlterField(
+            model_name='note',
+            name='title',
+            field=models.CharField(help_text="Give your note a good headline. What will your upcoming show be about? Try to arouse interest to listen to it!<br>Avoid technical data like the show's name, its airing times or its episode number. These data are added automatically.", max_length=128, verbose_name='Title'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='description',
+            field=tinymce.models.HTMLField(blank=True, help_text='Describe your show in detail.', null=True, verbose_name='Description'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='email',
+            field=models.EmailField(blank=True, help_text='The main contact email address for your show.', max_length=254, null=True, verbose_name='E-Mail'),
+        ),
         migrations.AlterField(
             model_name='show',
             name='image',
-            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', null=True, upload_to='show_images', verbose_name='Image', width_field='width'),
+            field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', help_text="Upload an image to your show. Images are automatically cropped around the 'Primary Point of Interest'. Click in the image to change it and press Save.", null=True, upload_to='show_images', verbose_name='Image', width_field='width'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='name',
+            field=models.CharField(help_text="The show's name. Avoid a subtitle.", max_length=255, verbose_name='Name'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='short_description',
+            field=models.TextField(help_text='Describe your show in some sentences. Avoid technical data like airing times and contact information. They will be added automatically.', verbose_name='Short description'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='slug',
+            field=models.CharField(help_text='A simple to read URL for your show', max_length=255, unique=True, verbose_name='Slug'),
+        ),
+        migrations.AlterField(
+            model_name='show',
+            name='website',
+            field=models.URLField(blank=True, help_text='Is there a website to your show? Type in its URL.', null=True, verbose_name='Website'),
         ),
         migrations.AlterField(
             model_name='timeslot',
diff --git a/program/models.py b/program/models.py
index a5f8b494d887fe48b48959ddba800a43cb8cdb78..a3a56598ca1de3b4d46359173fafa74ad5e199d6 100644
--- a/program/models.py
+++ b/program/models.py
@@ -566,6 +566,7 @@ class TimeSlot(models.Model):
     show = models.ForeignKey(Show, editable=False, related_name='timeslots')
     memo = models.TextField(_("Memo"), blank=True)
     is_repetition = models.BooleanField(_("WH"), default=False)
+    playlist_id = models.IntegerField(_("Playlist ID"), null=True)
 
     objects = TimeSlotManager()
 
@@ -604,7 +605,7 @@ class Note(models.Model):
     timeslot = models.OneToOneField(TimeSlot, verbose_name=_("Time slot"), unique=True)
     title = models.CharField(_("Title"), max_length=128, help_text=_("Give your note a good headline. What will your upcoming show be about? Try to arouse interest to listen to it!<br>Avoid technical data like the show's name, its airing times or its episode number. These data are added automatically."))
     slug = models.SlugField(_("Slug"), max_length=32, unique=True, help_text=_("A simple to read URL for your show."))
-    summary = tinymce_models.HTMLField(_("Summary"), blank=True, help_text=_("Describe your upcoming show in some sentences. Avoid technical data like airing times and contact information. They will be added automatically."))
+    summary = models.TextField(_("Summary"), blank=True, help_text=_("Describe your upcoming show in some sentences. Avoid technical data like airing times and contact information. They will be added automatically."))
     content = tinymce_models.HTMLField(_("Content"), help_text=_("Describe your upcoming show in detail."))
     ppoi = PPOIField('Image PPOI')
     height = models.PositiveIntegerField('Image Height', blank=True, null=True, editable=False)
diff --git a/program/serializers.py b/program/serializers.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc82e6089b2e090482f0100794cb2ed9de2ac95f
--- /dev/null
+++ b/program/serializers.py
@@ -0,0 +1,177 @@
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.auth.models import User
+from rest_framework import serializers, status
+from rest_framework.response import Response
+from program.models import Show, TimeSlot, Category, Host, Language, Topic, MusicFocus, Note
+from profile.models import Profile
+
+
+class ProfileSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Profile
+        fields = '__all__'
+
+
+class UserSerializer(serializers.ModelSerializer):
+    # Add profile fields to JSON
+    profile = ProfileSerializer()
+
+    class Meta:
+        model = User
+        exclude = ('password',)
+        #fields = '__all__'
+
+
+    def update(self, instance, validated_data):
+        """
+        Update and return an existing User instance, given the validated data.
+        """
+
+        instance.first_name = validated_data.get('first_name', instance.first_name)
+        instance.last_name = validated_data.get('last_name', instance.last_name)
+        instance.email = validated_data.get('email', instance.email)
+
+        profile = Profile.objects.get(user=instance.id)
+        profile.biography = validated_data['profile'].get('biography')
+        profile.website = validated_data['profile'].get('website')
+        profile.googleplus_url = validated_data['profile'].get('googleplus_url')
+        profile.facebook_url = validated_data['profile'].get('facebook_url')
+        profile.twitter_url = validated_data['profile'].get('twitter_url')
+        profile.linkedin_url = validated_data['profile'].get('linkedin_url')
+        profile.youtube_url = validated_data['profile'].get('youtube_url')
+        profile.dorftv_url = validated_data['profile'].get('dorftv_url')
+        profile.cba_url = validated_data['profile'].get('cba_url')
+        profile.cba_username = validated_data['profile'].get('cba_username')
+        profile.cba_user_token = validated_data['profile'].get('cba_user_token')
+        profile.save()
+
+        instance.save()
+        return instance
+
+
+class CategorySerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Category
+        fields = '__all__'
+
+
+class HostSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Host
+        fields = '__all__'
+
+
+class LanguageSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Language
+        fields = '__all__'
+
+
+class TopicSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Topic
+        fields = '__all__'
+
+
+class MusicFocusSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = MusicFocus
+        fields = '__all__'
+
+
+class ShowSerializer(serializers.HyperlinkedModelSerializer):
+    category = CategorySerializer(many=True)
+    hosts = HostSerializer(many=True)
+    language = LanguageSerializer(many=True)
+    topic = TopicSerializer(many=True)
+    musicfocus = MusicFocusSerializer(many=True)
+
+    class Meta:
+        model = Show
+        fields = ('id', 'name', 'slug', 'image', 'logo', 'short_description', 'description',
+                  'email', 'website', 'created', 'last_updated', 'type_id', 'rtrcategory_id',
+                  'predecessor_id', 'cba_series_id', 'fallback_pool', 'category', 'hosts',
+                  'language', 'topic', 'musicfocus')
+
+
+    def create(self, validated_data):
+        """
+        Create and return a new Show instance, given the validated data.
+        """
+        return Show.objects.create(**validated_data)
+
+
+    def update(self, instance, validated_data):
+        """
+        Update and return an existing Show instance, given the validated data.
+        """
+
+        instance.name = validated_data.get('name', instance.name)
+        instance.slug = validated_data.get('slug', instance.slug)
+        instance.image = validated_data.get('image', instance.image)
+        instance.logo = validated_data.get('logo', instance.logo)
+        instance.short_description = validated_data.get('short_description', instance.short_description)
+        instance.description = validated_data.get('description', instance.description)
+        instance.email = validated_data.get('email', instance.email)
+        instance.website = validated_data.get('website', instance.website)
+        instance.cba_series_id = validated_data.get('cba_series_id', instance.cba_series_id)
+        instance.fallback_pool = validated_data.get('fallback_pool', instance.fallback_pool)
+        instance.save()
+        return instance
+
+
+class TimeSlotSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = TimeSlot
+        fields = '__all__'
+
+
+    def create(self, validated_data):
+        """
+        Create and return a new TimeSlot instance, given the validated data.
+        """
+        return TimeSlot.objects.create(**validated_data)
+
+
+    def update(self, instance, validated_data):
+        """
+        Update and return an existing Show instance, given the validated data.
+        """
+
+        instance.memo = validated_data.get('memo', instance.memo)
+        instance.is_repetition = validated_data.get('is_repetition', instance.is_repetition)
+        instance.playlist_id = validated_data.get('playlist_id', instance.playlist_id)
+        instance.save()
+        return instance
+
+
+class NoteSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Note
+        fields = '__all__'
+
+
+    def create(self, validated_data):
+        """
+        Create and return a new Note instance, given the validated data.
+        """
+
+        return Note.objects.create(**validated_data)
+
+
+    def update(self, instance, validated_data):
+        """
+        Update and return an existing Note instance, given the validated data.
+        """
+
+        instance.show_id = validated_data.get('show_id', instance.show_id)
+        instance.timeslot_id = validated_data.get('timeslot_id', instance.timeslot_id)
+        instance.title = validated_data.get('title', instance.title)
+        instance.slug = validated_data.get('slug', instance.slug)
+        instance.summary = validated_data.get('summary', instance.summary)
+        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.cba_id = validated_data.get('cba_id', instance.cba_id)
+        instance.save()
+        return instance
\ No newline at end of file
diff --git a/program/templates/calendar.html b/program/templates/calendar.html
index 318f052ebb5cf638b0bd3aba685ece1f0eab590a..4f8d7f4f153c4a314bf68a456f603cfee332b6d0 100644
--- a/program/templates/calendar.html
+++ b/program/templates/calendar.html
@@ -67,6 +67,14 @@
        font-size:.8em;
     }
 
+    .default {
+       background-color:#3a87ad;
+    }
+    .danger {
+       background-color:#D60935;
+       border-color:#222;
+    }
+
     </style>
 
 </head>
@@ -85,14 +93,23 @@
     <div class="calendar-container">
       <div id="calendar"></div>
       <div id="sidebar">
-        <div id="timeslot-id"></div>
+        <!--<div id="timeslot-id"></div>-->
+        <div>
+          <span id="show-name"></span>
+          <span id="show-id"></span>
+        </div>
         <div id="timeslot-start"></div>
         <div id="timeslot-end"></div>
-        <div id="show-name"></div>
-        <div id="show-id"></div>
-        <div id="show-hosts"></div>
+        <p></p>
+        <div id="playlist-id"></div>
         <div id="is-repetition"></div>
         <div id="fallback-playlist-id"></div>
+        <p></p>
+        <div id="show-hosts"></div>
+        <div id="show-categories"></div>
+        <div id="show-type"></div>
+        <div id="show-musicfocus"></div>
+        <div id="show-rtrcategory"></div>
         <div id="response-message"></div>
       </div>
     </div>
@@ -130,7 +147,7 @@
           },
           weekNumberCalculation: 'ISO', // Week begins with Monday
           firstDay: 1, // Week begins with Monday
-          events: '/export/week_schedule',
+          events: '/api/v1/week_schedule',
           eventRender: function(event, element) {
              element.find('.fc-content').append( '<span class="closeon">X</span>' );
              element.find('.closeon').click(function() {
@@ -139,7 +156,7 @@
                    return false;
 
                 // Delete from database
-                jQuery.post( '/program/calendar/', { 'action': 'delete_timeslot', 'id': event._id } )
+                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 );
@@ -164,31 +181,21 @@
           // Load the timeslot into the sidebar form
           eventClick: function(calEvent, jsEvent, view) {
           	console.log(calEvent);
-            jQuery.ajax({
-              url: '/export/get_timeslot',
-              type: 'GET',
-              data: {
-                'timeslot_id': calEvent.id,
-                'csrfmiddlewaretoken': jQuery('input[name="csrfmiddlewartetoken"]').val()
-              },
-              success: function(timeslot) {
-                jQuery("#timeslot-id").html(timeslot.id);
-                jQuery("#timeslot-start").html(timeslot.start);
-                jQuery("#timeslot-end").html(timeslot.end);
-                jQuery("#show-name").html(timeslot.show_name);
-                jQuery("#show-id").html(timeslot.show_id);
-                jQuery("#show-hosts").html(timeslot.show_hosts);
-                jQuery("#is-repetition").html(timeslot.is_repetition);
-                jQuery("#fallback-playlist-id").html(timeslot.fallback_playlist_id);
-                jQuery("#memo").html(timeslot.memo);
-                jQuery("#response-message").html("Success");
-              },
-              error: function() {
-                jQuery("#response-message").html("An error occured");
-              }
-
-            });
 
+            jQuery("#timeslot-id").html(calEvent.id);
+            jQuery("#timeslot-start").html('Start: ' + moment(calEvent.start).format("DD.MM. YYYY HH:SS"));
+            jQuery("#timeslot-end").html('End: ' + moment(calEvent.end).format("DD.MM. YYYY HH:SS"));
+            jQuery("#show-name").html(calEvent.show_name);
+            jQuery("#show-id").html('(ID ' + calEvent.show_id + ')');
+            jQuery("#show-hosts").html('Hosts: ' + calEvent.show_hosts);
+            jQuery("#show-type").html('Type: ' + calEvent.show_type);
+            jQuery("#show-categories").html('Categories: ' + calEvent.show_categories);
+            jQuery("#show-topics").html('Topics: ' + calEvent.show_topics);
+            jQuery("#show-musicfocus").html('Music focus: ' + calEvent.show_musicfocus);
+            jQuery("#is-repetition").html('WH: ' + calEvent.is_repetition);
+            jQuery("#playlist-id").html('Playlist ID: ' + calEvent.playlist_id);
+            jQuery("#fallback-playlist-id").html('Fallback ID: ' + calEvent.fallback_playlist_id);
+            jQuery("#memo").html(calEvent.memo);
 
           },
           // How is this callback triggered?
diff --git a/program/views.py b/program/views.py
index 70471c00ab6595414f753b86da50767c38561b9b..efef269ed1e4e34f1c27d3b991721d748e99216c 100644
--- a/program/views.py
+++ b/program/views.py
@@ -2,15 +2,25 @@ import json
 from datetime import date, datetime, time, timedelta
 
 from django.db.models import Q
+from django.utils.translation import ugettext_lazy as _
 from django.core.exceptions import ObjectDoesNotExist
 from django.forms.models import model_to_dict
-from django.http import HttpResponse, JsonResponse
+from django.contrib.auth.models import User
+from django.http import Http404, HttpResponse, JsonResponse
 from django.shortcuts import get_object_or_404
 from django.views.generic.base import TemplateView
 from django.views.generic.detail import DetailView
 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.decorators import detail_route, list_route
 
-from .models import Type, MusicFocus, Note, Show, Category, Topic, TimeSlot, Host
+from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
+
+from program.models import Type, MusicFocus, Note, Show, Category, RTRCategory, Topic, TimeSlot, Host
+from profile.models import Profile
+from program.serializers import ShowSerializer, TimeSlotSerializer, UserSerializer, NoteSerializer
 from program.utils import tofirstdayinisoweek, get_cached_shows
 
 
@@ -229,9 +239,7 @@ def json_week_schedule(request):
     Returns all timeslots of the next 7 days
     """
 
-    start = request.GET.get('start')
-
-    if start == None:
+    if request.GET.get('start') == None:
         start = datetime.combine(date.today(), time(0, 0))
     else:
         start = datetime.combine( datetime.strptime(request.GET.get('start'), '%Y-%m-%d').date(), time(0, 0))
@@ -242,26 +250,42 @@ def json_week_schedule(request):
 
         is_repetition = ' ' + _('WH') if ts.schedule.is_repetition is 1 else ''
 
-        hosts = ''
+        hosts = ', '.join(ts.show.hosts.values_list('name', flat=True))
+        categories = ', '.join(ts.show.category.values_list('category', flat=True))
+        topics = ', '.join(ts.show.topic.values_list('topic', flat=True))
+        musicfocus = ', '.join(ts.show.musicfocus.values_list('focus', flat=True))
+        languages = ', '.join(ts.show.language.values_list('name', flat=True))
+        rtrcategory = RTRCategory.objects.get(pk=ts.show.rtrcategory_id)
+        type = Type.objects.get(pk=ts.show.type_id)
+
+        classname = 'default'
 
-        for host in ts.show.hosts.all():
-            hosts = host.name + ', ' + hosts
+        if ts.playlist_id is None or ts.playlist_id == 0:
+            classname = 'danger'
 
         entry = {
+            'id': ts.id,
             'start': ts.start.strftime('%Y-%m-%dT%H:%M:%S'),
             'end': ts.end.strftime('%Y-%m-%dT%H:%M:%S'),
-            'title': ts.show.name + is_repetition,
-            'id': ts.id, #show.id,
+            'title': ts.show.name + is_repetition, # For JS Calendar
             'automation-id': -1,
             '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
             'show_id': ts.show.id,
-            'show_name': ts.show.name,
+            'show_name': ts.show.name + is_repetition,
             'show_hosts': hosts,
-            'is_repetition': ts.is_repetition,
-            'fallback_playlist_id': ts.schedule.fallback_playlist_id, # the schedule's fallback playlist
-            'show_fallback_pool': ts.show.fallback_pool, # the show's fallback
-            # TODO
-            #'station_fallback_pool': # the station's global fallback (might change suddenly)
+            'show_type': type.type,
+            'show_categories': categories,
+            'show_topics': topics,
+            'show_musicfocus': musicfocus,
+            'show_languages': languages,
+            'show_rtrcategory': rtrcategory.rtrcategory,
+            'station_fallback_id': 0, # TODO: The station's global fallback (might change)
+            'memo': ts.memo,
+            'className': classname,
         }
 
         if ts.schedule.automation_id:
@@ -299,40 +323,22 @@ def json_timeslots_specials(request):
                         content_type="application/json; charset=utf-8")
 
 
-def json_get_timeslot(request):
-    if not request.user.is_authenticated():
-        return JsonResponse(_('Permission denied.'))
-
-    if request.method == 'GET':
-        try:
-            timeslot = TimeSlot.objects.get(pk=int(request.GET.get('timeslot_id')))
-
-            returnvar = { 'id': timeslot.id, 'start': timeslot.start, 'end': timeslot.end,
-                          'schedule_id': timeslot.schedule.id, 'show_name': timeslot.show.name,
-                          'is_repetition': timeslot.schedule.is_repetition,
-                          'fallback_playlist_id': timeslot.schedule.fallback_playlist_id,
-                          'memo': timeslot.memo }
-            return JsonResponse( returnvar, safe=False )
-        except ObjectDoesNotExist:
-            return JsonResponse( _('Error') );
-
-
 def json_get_timeslots_by_show(request):
     '''
     Returns a JSON object of timeslots of a given show from 4 weeks ago until 12 weeks in the future
-    Called by /export/get_timeslot_by_show/?show_id=1 to populate a timeslot-select for being assigned to a note
+    Called by /api/v1/timeslots/?show_id=1 to populate a timeslot-select for being assigned to a note
     '''
 
     if not request.user.is_authenticated():
         return JsonResponse(_('Permission denied.'))
 
-    if request.method == 'GET' and int(request.GET.get('show_id')):
+    if request.method == 'GET' and request.GET.get('show_id') != None:
 
         four_weeks_ago = datetime.now() - timedelta(weeks=4)
         in_twelve_weeks = datetime.now() + timedelta(weeks=12)
 
         timeslots = []
-        saved_timeslot_id = int(request.GET.get('timeslot_id'))
+        saved_timeslot_id = 0 if request.GET.get('timeslot_id') == None else int(request.GET.get('timeslot_id'))
 
         # If the saved timeslot is part of the currently selected show,
         # include it as the first select-option in order not to lose it if it's past
@@ -352,4 +358,307 @@ def json_get_timeslots_by_show(request):
         return JsonResponse( timeslots, safe=False )
 
     else:
-        return JsonResponse( _('No show_id given.'), safe=False )
\ No newline at end of file
+        return JsonResponse( _('No show_id given.'), safe=False )
+
+
+
+
+####################################################################
+# REST API View Sets
+####################################################################
+
+
+class APIUserViewSet(viewsets.ModelViewSet):
+    """
+    /api/v1/users   Returns oneself - Superusers see all users
+    /api/v1/users/1 Used for retrieving or updating a single user
+
+    Superusers may access and update all users
+    """
+
+    permission_classes = [permissions.IsAuthenticated, permissions.DjangoModelPermissions] #, TokenHasReadWriteScope]
+    serializer_class = UserSerializer
+    queryset = User.objects.all()
+    required_scopes = ['users']
+
+    def list(self, request):
+        # Commons users only see themselves
+        if request.user.is_superuser:
+            users = User.objects.all()
+        else:
+            users = User.objects.filter(pk=request.user.id)
+
+        serializer = UserSerializer(users, many=True)
+        return Response(serializer.data)
+
+
+    def retrieve(self, request, pk=None):
+        """Returns a single user"""
+        if pk != None:
+            pk = int(pk)
+            try:
+                user = User.objects.get(pk=pk)
+            except ObjectDoesNotExist:
+                return Response(status=status.HTTP_404_NOT_FOUND)
+
+            # Common users may only see themselves
+            if not request.user.is_superuser and user.id != request.user.id:
+                return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+            serializer = UserSerializer(user)
+            return Response(serializer.data)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+    def partial_update(self, request, pk=None):
+
+        # Common users may only edit themselves
+        if not request.user.is_superuser and int(pk) != request.user.id:
+            return Response(serializer.initial_data, status=status.HTTP_401_UNAUTHORIZED)
+
+        serializer = UserSerializer(data=request.data)
+
+        if serializer.is_valid():
+            serializer.save();
+            return Response(serializer.data)
+
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+class APIShowViewSet(viewsets.ModelViewSet):
+    """
+    /api/v1/shows/  Returns shows a user owns
+    /api/v1/shows/1 Used for retrieving a single show or update (if owned)
+
+    Superusers may access and update all shows
+    """
+
+    queryset = Show.objects.all()
+    serializer_class = ShowSerializer
+    permission_classes = [permissions.IsAuthenticated, permissions.DjangoModelPermissions] #, TokenHasReadWriteScope]
+    required_scopes = ['shows']
+
+
+    def list(self, request):
+        """Lists all shows"""
+
+        # Commons users only see shows they own
+        if request.user.is_superuser:
+            shows = Show.objects.all()
+        else:
+            shows = request.user.shows.all()
+
+        serializer = ShowSerializer(shows, many=True)
+        return Response(serializer.data)
+
+
+    def create(self, request):
+        """Create is not allowed at the moment"""
+        return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+
+    def retrieve(self, request, pk=None):
+        """Returns a single show"""
+
+        if pk != None and int(pk):
+            pk = int(pk)
+
+            # Common users may only retrieve shows they own
+            if not request.user.is_superuser and pk not in list(request.user.shows.all().values_list('id', flat=True)):
+                return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+            try:
+                show = Show.objects.get(pk=pk)
+            except ObjectDoesNotExist:
+                return Response(status=status.HTTP_404_NOT_FOUND)
+
+            serializer = ShowSerializer(show)
+            return Response(serializer.data)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+    def partial_update(self, request, pk=None):
+        serializer = ShowSerializer(data=request.data)
+
+        # For common user and not owner of show: Permission denied
+        if not request.user.is_superuser and int(pk) not in list(request.user.shows.all().values_list('id', flat=True)):
+            return Response(serializer.initial_data, status=status.HTTP_401_UNAUTHORIZED)
+
+        if serializer.is_valid():
+            serializer.save();
+            return Response(serializer.data)
+
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+    def destroy(self, request, pk=None):
+        '''Deleting is not allowed at the moment'''
+        return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+
+class APITimeSlotViewSet(viewsets.ModelViewSet):
+    """
+    /api/v1/timeslots                                            Returns nothing
+    /api/v1/timeslots/?show_id=1                                 Returns upcoming timeslots of a show 60 days in the future
+    /api/v1/timeslots/?show_id=1&start=2017-01-01&end=2017-02-01 Returns timeslots of a show within the given timerange
+
+    TODO: Test for permissions to show
+    """
+
+    permission_classes = [permissions.IsAuthenticated, permissions.DjangoModelPermissions] #, TokenHasReadWriteScope]
+    serializer_class = TimeSlotSerializer
+    queryset = TimeSlot.objects.all()
+    required_scopes = ['timeslots']
+
+
+    def list(self, request):
+        """Lists timeslots of a show"""
+
+        if request.GET.get('show_id') != None:
+            show_id = int(request.GET.get('show_id'))
+
+            if not request.user.is_superuser and show_id not in list(request.user.shows.all().values_list('id', flat=True)):
+                return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+            # Return next 60 days by default
+            start = datetime.combine(date.today(), time(0, 0))
+            end = start + timedelta(days=60)
+
+            if request.GET.get('start') and request.GET.get('end'):
+                start = datetime.combine( datetime.strptime(request.GET.get('start'), '%Y-%m-%d').date(), time(0, 0))
+                end = datetime.combine( datetime.strptime(request.GET.get('end'), '%Y-%m-%d').date(), time(23, 59))
+
+            timeslots = TimeSlot.objects.filter(show=show_id, start__gte=start, end__lte=end).order_by('start')
+            serializer = TimeSlotSerializer(timeslots, many=True)
+
+            return Response(serializer.data)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+    def create(self, request):
+        return Response(status=HTTP_401_UNAUTHORIZED)
+
+
+    def partial_update(self, request, pk=None):
+        """Link a playlist_id to a timeslot"""
+
+        serializer = TimeSlotSerializer(data=request.data)
+        if serializer.is_valid():
+            serializer.save()
+            return Response(serializer.data, status=status.HTTP_200_OK)
+
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+    def destroy(self, request, pk=None):
+        """Deleting is not allowed at the moment"""
+        return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+
+
+class APINoteViewSet(viewsets.ModelViewSet):
+    """
+    /api/v1/notes/                Returns nothing
+    /ap1/v1/notes/1               Returns a single not (if owned)
+    /api/v1/notes/?ids=1,2,3,4,5  Returns given notes (if owned)
+
+    Superusers may access and update all notes
+    """
+
+    queryset = Note.objects.all()
+    serializer_class = NoteSerializer
+    permission_classes = [permissions.IsAuthenticated, permissions.DjangoModelPermissions] #, TokenHasReadWriteScope]
+    required_scopes = ['notes']
+
+
+    def list(self, request):
+        """Lists notes"""
+
+        if request.GET.get('ids') != None:
+            note_ids = request.GET.get('ids').split(',')
+            if 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=request.user.id)
+        else:
+           notes = Note.objects.none()
+
+        serializer = NoteSerializer(notes, many=True)
+        return Response(serializer.data)
+
+
+    def create(self, request):
+        """
+        Creates a note
+        TODO: Test!
+        """
+
+        # Only create a note if show_id and timeslot_id is given
+        if not int(validated_data.get('show_id')) and not int(validated_data.get('timeslot_id')):
+            return Response(status=status.HTTP_400_BAD_REQUEST)
+
+        serializer = NoteSerializer(data=request.data)
+        if serializer.is_valid():
+            serializer.save()
+            return Response(serializer.data)
+
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+    def retrieve(self, request, pk=None):
+        """Returns a single note"""
+
+        if pk != None:
+            try:
+                note = Note.objects.get(pk=pk)
+            except ObjectDoesNotExist:
+                return Response(status=status.HTTP_404_NOT_FOUND)
+
+            if not request.user.is_superuser and note.user_id != request.user.id:
+                return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+            serializer = NoteSerializer(note)
+            return Response(serializer.data)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+    def partial_update(self, request, pk=None):
+        if pk != None:
+            pk = int(pk)
+            try:
+                note = Note.objects.get(pk=pk)
+            except ObjectDoesNotExist:
+                return Response(status=status.HTTP_404_NOT_FOUND)
+
+            if not request.user.is_superuser and note.user_id != request.user.id:
+                return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+            serializer = NoteSerializer(data=request.data)
+
+            if serializer.is_valid():
+                serializer.save();
+                return Response(serializer.data)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+    def destroy(self, request, pk=None):
+        if pk != None:
+            pk = int(pk)
+            try:
+                note = Note.objects.get(pk=pk)
+            except ObjectDoesNotExist:
+                return Response(status.HTTP_404_NOT_FOUND)
+
+            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)
+
+        return Response(status=status.HTTP_400_BAD_REQUEST)
\ No newline at end of file
diff --git a/pv/settings.py b/pv/settings.py
index 654c0caea608971ee26c9714bbab190450c412d0..ca142790d1a9423a931f81c5eec767cd7c09cfd9 100644
--- a/pv/settings.py
+++ b/pv/settings.py
@@ -77,6 +77,23 @@ MIDDLEWARE_CLASSES = (
 
 ROOT_URLCONF = 'pv.urls'
 
+OAUTH2_PROVIDER = {
+    # this is the list of available scopes
+    'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'shows': 'Access to shows', 'notes': 'Access to notes', 'timeslots': 'Access to timeslots'}
+}
+
+REST_FRAMEWORK = {
+    # Use Django's standard `django.contrib.auth` permissions,
+    # or allow read-only access for unauthenticated users.
+    'DEFAULT_PERMISSION_CLASSES': [
+        'rest_framework.permissions.IsAuthenticated',
+        #'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
+    ],
+    'DEFAULT AUTHENTICATION_CLASSES': [
+        'oauth2_provider.ext.rest_framework.OAuth2Authentication',
+    ]
+}
+
 INSTALLED_APPS = (
     'django.contrib.auth',
     'django.contrib.contenttypes',
@@ -90,15 +107,24 @@ INSTALLED_APPS = (
     'profile',
     'tinymce',
     'versatileimagefield',
+    'rest_framework',
+    'oauth2_provider',
 )
 
 THUMBNAIL_SIZES = ['200x200', '150x150']
 
-TINYMCE_JS_URL = '/static/js/tiny_mce/tiny_mce.js'
+#TINYMCE_JS_URL = '/static/js/tiny_mce/tiny_mce.js'
 TINYMCE_DEFAULT_CONFIG = {
-    'plugins': 'contextmenu',
+    #'plugins': 'contextmenu',
+    'selector': 'textarea',
     'theme': 'advanced',
     'theme_advanced_toolbar_location': 'top',
+    'theme_advanced_buttons1' : 'bold,italic,underline,separator,bullist,numlist,separator,link,unlink,separator,undo,redo,separator,formatselect',
+    'theme_advanced_blockformats': 'p,h1,h2,h3,blockquote',
+    'theme_advanced_font_sizes': '14px,16px',
+    'cleanup_on_startup': True,
+    'width': 620,
+    'height': 400,
 }
 
 CACHE_BACKEND = 'locmem://'
diff --git a/pv/urls.py b/pv/urls.py
index 69ca4947c67d2cc2c1829d1d40418d7794814176..ff20888bc11d29c3027d8e25c6d94bbfb7c31369 100644
--- a/pv/urls.py
+++ b/pv/urls.py
@@ -2,21 +2,35 @@ from django.conf import settings
 from django.conf.urls import url, include
 from django.contrib import admin
 from django.views.static import serve
+from rest_framework import routers
+from rest_framework.authtoken import views
 
-from program.views import json_day_schedule, json_week_schedule, json_timeslots_specials, json_get_timeslot, json_get_timeslots_by_show
+from program.views import APIUserViewSet, APIShowViewSet, APITimeSlotViewSet, APINoteViewSet, json_day_schedule, json_week_schedule, json_timeslots_specials, json_get_timeslots_by_show
+
+from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
 
 admin.autodiscover()
 
+router = routers.DefaultRouter()
+router.register(r'users', APIUserViewSet)
+router.register(r'shows', APIShowViewSet)
+router.register(r'timeslots', APITimeSlotViewSet)
+router.register(r'notes', APINoteViewSet)
+
 urlpatterns = [
+    url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
+    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
+    url(r'^api-token-auth/', views.obtain_auth_token),
+    url(r'^api/v1/', include(router.urls) ),
+    url(r'^api/v1/timeslots-by-show$', json_get_timeslots_by_show, name='json_get_timeslots_by_show'),
     url(r'^admin/', admin.site.urls),
     url(r'^program/', include('program.urls')),
     url(r'^nop', include('nop.urls')),
     url(r'^tinymce/', include('tinymce.urls')),
     url(r'^export/day_schedule/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', json_day_schedule),
-    url(r'^export/week_schedule$', json_week_schedule),
+    url(r'^api/v1/program$', json_week_schedule),
+    url(r'^api/v1/week_schedule$', json_week_schedule),
     url(r'^export/timeslots_specials.json$', json_timeslots_specials),
-    url(r'^export/get_timeslot$', json_get_timeslot, name='get-timeslot'),
-    url(r'^export/get_timeslots_by_show$', json_get_timeslots_by_show, name='get-timeslots-by-show'),
 ]
 
 if settings.DEBUG:
diff --git a/requirements.txt b/requirements.txt
index 1a32e6057c7de8d2891e470f589a34ff2cff9d60..d1ce91ba7d65e4cb941abca43a2e01c6f756cac0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,6 @@ Pillow==4.2.1
 PyYAML==3.12
 django-tinymce==2.6.0
 python-dateutil==2.6.0
-django-versatileimagefield==1.8.1
\ No newline at end of file
+django-versatileimagefield==1.8.1
+djangorestframework
+django-oauth-toolkit
\ No newline at end of file