diff --git a/backend/core/admin.py b/backend/core/admin.py
index 9598b953d8bcb6c9cd0045ee101dbe133c295da7..7de2ec69610f75e0a7ee00849683bac6e5fc3e2f 100644
--- a/backend/core/admin.py
+++ b/backend/core/admin.py
@@ -1,85 +1,19 @@
-from django import forms
 from django.contrib import admin
-from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
-from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.models import Group
 
 from core.models import (Feedback, Reviewer, Student, Submission,
-                         SubmissionType, Test, Tutor, UserAccount)
+                         SubmissionType, Test, Tutor, UserAccount, ExamType)
 
-
-class UserCreationForm(forms.ModelForm):
-    """A form for creating new users. Includes all the required
-    fields, plus a repeated password."""
-    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
-    password2 = forms.CharField(
-        label='Password confirmation', widget=forms.PasswordInput)
-
-    class Meta:
-        model = UserAccount
-        fields = ()
-
-    def clean_password2(self):
-        # Check that the two password entries match
-        password1 = self.cleaned_data.get("password1")
-        password2 = self.cleaned_data.get("password2")
-        if password1 and password2 and password1 != password2:
-            raise forms.ValidationError("Passwords don't match")
-        return password2
-
-    def save(self, commit=True):
-        # Save the provided password in hashed format
-        user = super(UserCreationForm, self).save(commit=False)
-        user.set_password(self.cleaned_data["password1"])
-        if commit:
-            user.save()
-        return user
-
-
-class UserChangeForm(forms.ModelForm):
-    """A form for updating users. Includes all the fields on
-    the user, but replaces the password field with admin's
-    password hash display field.
-    """
-    password = ReadOnlyPasswordHashField()
-
-    class Meta:
-        model = UserAccount
-        fields = ('password', 'is_active', 'is_admin')
-
-    def clean_password(self):
-        return self.initial["password"]
-
-
-class UserAdmin(BaseUserAdmin):
-    # The forms to add and change user instances
-    form = UserChangeForm
-    add_form = UserCreationForm
-
-    # The fields to be used in displaying the User model.
-    # These override the definitions on the base UserAdmin
-    # that reference specific fields on auth.User.
-    list_display = ('username', 'is_admin',)
-    list_filter = ('is_admin',)
-    fieldsets = (
-        (None, {'fields': ('password',)}),
-        ('Permissions', {'fields': ('is_admin',)}),
-    )
-    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
-    # overrides get_fieldsets to use this attribute when creating a user.
-    add_fieldsets = (
-        (None, {
-            'classes': ('wide',),
-            'fields': ('password1', 'password2')}
-         ),
-    )
-    filter_horizontal = ()
-
-
-admin.site.register(UserAccount, UserAdmin)
+# Stuff we needwant
+admin.site.register(UserAccount)
 admin.site.register(SubmissionType)
 admin.site.register(Feedback)
 admin.site.register(Test)
+admin.site.register(ExamType)
 admin.site.register(Submission)
 admin.site.register(Reviewer)
 admin.site.register(Student)
 admin.site.register(Tutor)
+
+# ... and stuff we don't needwant
+admin.site.unregister(Group)
diff --git a/backend/core/migrations/0002_auto_20171110_1612.py b/backend/core/migrations/0002_auto_20171110_1612.py
new file mode 100644
index 0000000000000000000000000000000000000000..51ea77b9d911e7133d6fd9f8f4d1c6d52d9b90d5
--- /dev/null
+++ b/backend/core/migrations/0002_auto_20171110_1612.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.7 on 2017-11-10 16:12
+from __future__ import unicode_literals
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0008_alter_user_username_max_length'),
+        ('core', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='useraccount',
+            options={'verbose_name': 'user', 'verbose_name_plural': 'users'},
+        ),
+        migrations.AlterModelManagers(
+            name='useraccount',
+            managers=[
+                ('objects', django.contrib.auth.models.UserManager()),
+            ],
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='date_joined',
+            field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'),
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='email',
+            field=models.EmailField(blank=True, max_length=254, verbose_name='email address'),
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='first_name',
+            field=models.CharField(blank=True, max_length=30, verbose_name='first name'),
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='groups',
+            field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='last_name',
+            field=models.CharField(blank=True, max_length=30, verbose_name='last name'),
+        ),
+        migrations.AddField(
+            model_name='useraccount',
+            name='user_permissions',
+            field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
+        ),
+        migrations.AlterField(
+            model_name='useraccount',
+            name='is_active',
+            field=models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active'),
+        ),
+        migrations.AlterField(
+            model_name='useraccount',
+            name='is_staff',
+            field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'),
+        ),
+        migrations.AlterField(
+            model_name='useraccount',
+            name='is_superuser',
+            field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'),
+        ),
+        migrations.AlterField(
+            model_name='useraccount',
+            name='username',
+            field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
+        ),
+    ]
diff --git a/backend/core/models.py b/backend/core/models.py
index 3124992c15475cd0b6cbc939d66780cc26f961c3..32189b7e4f1c9f88c313e528b86583a216112b5b 100644
--- a/backend/core/models.py
+++ b/backend/core/models.py
@@ -11,7 +11,7 @@ from random import randrange, sample
 from string import ascii_lowercase
 
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
+from django.contrib.auth.models import AbstractUser
 from django.db import models
 from django.db.models import Value as V
 from django.db.models import (BooleanField, Case, Count, F, IntegerField, Q,
@@ -150,64 +150,16 @@ class SubmissionType(models.Model):
             ).order_by('name')
 
 
-class UserAccountManager(BaseUserManager):
-
-    def create_user(self, password=None, **kwargs):
-        if not kwargs.get('username'):
-            raise ValueError('Users must have a valid username.')
-
-        account = self.model(
-            username=kwargs.get('username')
-        )
-
-        account.set_password(password)
-        account.save()
-
-        return account
-
-    def create_superuser(self, password, **kwargs):
-        account = self.create_user(password, **kwargs)
-
-        account.is_admin = True
-        account.is_staff = True
-        account.is_superuser = True
-        account.save()
-
-        return account
-
-
-class UserAccount(AbstractBaseUser):
+class UserAccount(AbstractUser):
     """
     An abstract base class implementing a fully featured User model with
     admin-compliant permissions.
 
     Username and password are required. Other fields are optional.
     """
-    objects = UserAccountManager()
-
-    username = models.CharField(
-        max_length=150,
-        unique=True,
-        error_messages={
-            'unique': "A user with that username already exists.",
-        },
-    )
 
     fullname = models.CharField('full name', max_length=70, blank=True)
-
-    is_staff = models.BooleanField('staff status', default=False)
     is_admin = models.BooleanField(default=False)
-    is_superuser = models.BooleanField(default=False)
-
-    is_active = models.BooleanField('active', default=True)
-
-    USERNAME_FIELD = 'username'
-
-    def has_perm(self, *args):
-        return self.is_superuser
-
-    def has_module_perms(self, *args):
-        return self.is_superuser
 
     def get_associated_user(self):
         """ Returns the user type that is associated with this user obj """
@@ -216,12 +168,6 @@ class UserAccount(AbstractBaseUser):
             (hasattr(self, 'reviewer') and self.reviewer) or \
             (hasattr(self, 'tutor') and self.tutor)
 
-    def get_short_name(self):
-        return self.username
-
-    def get_username(self):
-        return self.username
-
 
 class Tutor(models.Model):
     user = models.OneToOneField(
@@ -535,10 +481,10 @@ class Feedback(models.Model):
         ACCEPTED,
     ) = range(4)  # this order matters
     STATUS = (
-        (EDITABLE,      'editable'),
-        (OPEN,          'request reassignment'),
-        (NEEDS_REVIEW,  'request review'),
-        (ACCEPTED,      'accepted'),
+        (EDITABLE, 'editable'),
+        (OPEN, 'request reassignment'),
+        (NEEDS_REVIEW, 'request review'),
+        (ACCEPTED, 'accepted'),
     )
     status = models.IntegerField(
         choices=STATUS,
@@ -554,11 +500,11 @@ class Feedback(models.Model):
         MANUAL,
     ) = range(5)
     ORIGIN = (
-        (WAS_EMPTY,         'was empty'),
+        (WAS_EMPTY, 'was empty'),
         (FAILED_UNIT_TESTS, 'passed unittests'),
-        (DID_NOT_COMPILE,   'did not compile'),
-        (COULD_NOT_LINK,    'could not link'),
-        (MANUAL,            'created by a human. yak!'),
+        (DID_NOT_COMPILE, 'did not compile'),
+        (COULD_NOT_LINK, 'could not link'),
+        (MANUAL, 'created by a human. yak!'),
     )
     origin = models.IntegerField(
         choices=ORIGIN,
diff --git a/backend/core/serializers.py b/backend/core/serializers.py
index 8178ecfae6f841340d82c7e9afada2bee9dd3c52..a5d7967f6d46e68132d215b3f577abec331c15ab 100644
--- a/backend/core/serializers.py
+++ b/backend/core/serializers.py
@@ -2,7 +2,6 @@ from rest_framework import serializers
 
 from core.models import ExamType, Feedback, Student, Submission
 
-
 class ExamSerializer(serializers.ModelSerializer):
 
     class Meta:
diff --git a/backend/core/views/api.py b/backend/core/views/api.py
index 3f015b444b638508b0849bf517d4990ca431478c..d0d45630fc5e653657f3302e5639cda68ed0d096 100644
--- a/backend/core/views/api.py
+++ b/backend/core/views/api.py
@@ -1,14 +1,18 @@
+import logging
+
 from rest_framework.generics import RetrieveAPIView
 
 from core.permissions import IsStudent
 from core.serializers import (FeedbackSerializer, StudentSerializer,
                               SubmissionSerializer)
 
+log = logging.getLogger(__name__)
 
 class StudentApiView(RetrieveAPIView):
     permission_classes = (IsStudent,)
 
     def get_object(self):
+        log.debug("Serializing student of user '%s'", self.request.user.username)
         return self.request.user.student
     serializer_class = StudentSerializer
 
diff --git a/backend/grady/settings/default.py b/backend/grady/settings/default.py
index deacf96ff165ddf6a11ac0079f4ab26d0df538bf..86e0c94956f52ceb7b459c8d8e015d73c4825a6c 100644
--- a/backend/grady/settings/default.py
+++ b/backend/grady/settings/default.py
@@ -159,3 +159,52 @@ REST_FRAMEWORK = {
 JWT_AUTH = {
     'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=600),
 }
+
+LOGGING = {
+    "version": 1,
+    "disable_existing_loggers": False,
+    "formatters": {
+        'django.server': {
+            'datefmt': '%d/%b/%Y %H:%M:%S',
+            'format': '[%(asctime)s] %(levelname)-10s %(name)-20s %(message)s',
+        },
+        'core': {
+            'datefmt': '%d/%b/%Y %H:%M:%S',
+            'format': '[%(asctime)s] %(levelname)-10s %(name)-20s "%(message)s"',
+        },
+    },
+    'filters': {
+        'require_debug_true': {
+            '()': 'django.utils.log.RequireDebugTrue',
+        },
+    },
+    'handlers': {
+        'console': {
+            'level': 'DEBUG',
+            'filters': ['require_debug_true'],
+            'class': 'logging.StreamHandler',
+            'formatter': 'core'
+        },
+        'django': {
+            'level': 'INFO',
+            'class': 'logging.StreamHandler',
+            'formatter': 'django.server'
+        },
+        'mail_admins': { # TODO: configuration
+            'level': 'ERROR',
+            'class': 'django.utils.log.AdminEmailHandler',
+        }
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['django'],
+        },
+        'django.request': {
+            'handlers': ['django'],
+        },
+        'core': {
+            'handlers': ['console', 'mail_admins'],
+            'level': 'DEBUG',
+        }
+    }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 0c946c7d11266e7cf0276ac7bb2fd8dfc0c7af10..ec065b746f5b597cf8db46c922d77fd50e1d6584 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,7 +9,7 @@
     "start": "npm run dev",
     "build": "node build/build.js",
     "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
-    "test": "npm run unit",
+    "test": "karma start test/unit/karma.conf.js",
     "lint": "eslint --ext .js,.vue src test/unit/specs"
   },
   "dependencies": {
@@ -17,6 +17,7 @@
     "bootstrap": "4.0.0-beta.2",
     "bootstrap-vue": "^1.0.0",
     "vue": "^2.5.2",
+    "vue-beautify": "^1.1.3",
     "vue-router": "^3.0.1",
     "vuex": "^3.0.1"
   },
@@ -36,6 +37,7 @@
     "copy-webpack-plugin": "^4.0.1",
     "cross-env": "^5.0.1",
     "css-loader": "^0.28.0",
+    "es6-promise": "^4.1.1",
     "eslint": "^3.19.0",
     "eslint-config-standard": "^10.2.1",
     "eslint-friendly-formatter": "^3.0.0",
diff --git a/frontend/test/unit/index.js b/frontend/test/unit/index.js
index c69f33fd8a0fb2796073222645c42c7c15188a6e..d0ddfd3846b6da1e934c141b7137655ccd6b13b2 100644
--- a/frontend/test/unit/index.js
+++ b/frontend/test/unit/index.js
@@ -1,4 +1,5 @@
 import Vue from 'vue'
+import 'es6-promise/auto'
 
 Vue.config.productionTip = false
 
diff --git a/frontend/test/unit/karma.conf.js b/frontend/test/unit/karma.conf.js
index 8e4951c9e4ecc597be347be1fd8e163cdbab13e2..b77a340f7b3796e08e7f60abee8bded5c26365df 100644
--- a/frontend/test/unit/karma.conf.js
+++ b/frontend/test/unit/karma.conf.js
@@ -7,10 +7,6 @@ var webpackConfig = require('../../build/webpack.test.conf')
 
 module.exports = function (config) {
   config.set({
-    // to run in additional browsers:
-    // 1. install corresponding karma launcher
-    //    http://karma-runner.github.io/0.13/config/browsers.html
-    // 2. add it to the `browsers` array below.
     browsers: ['PhantomJS'],
     frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
     reporters: ['spec', 'coverage'],
diff --git a/frontend/test/unit/specs/Hello.spec.js b/frontend/test/unit/specs/Hello.spec.js
index bfaa225c95f61e61e5e03bab02e25166f063199d..4f647728ecfde3ee9cc5bfd41ac94d0e507f8b65 100644
--- a/frontend/test/unit/specs/Hello.spec.js
+++ b/frontend/test/unit/specs/Hello.spec.js
@@ -1,11 +1,16 @@
 import Vue from 'vue'
-import HelloWorld from '@/components/HelloWorld'
+import Login from '@/components/Login'
 
-describe('HelloWorld.vue', () => {
-  it('should render correct contents', () => {
-    const Constructor = Vue.extend(HelloWorld)
-    const vm = new Constructor().$mount()
-    expect(vm.$el.querySelector('.hello h1').textContent)
-      .to.equal('Welcome to Your Vue.js App')
+describe('Login.vue', () => {
+  it('is doing nothing but ensure that we can test', () => {
+    data = {"submissions": [
+            {
+                "type": "Aufgabe 01",
+                "text": "Hallo, was geht denn da?",
+                "feedback": null,
+                "score": null,
+                "full_score": 12
+            }
+        ]}
   })
 })
diff --git a/frontend/test/unit/specs/Login.spec.js b/frontend/test/unit/specs/Login.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..127bc590abbd8db0880c9bde946570d041c4011a
--- /dev/null
+++ b/frontend/test/unit/specs/Login.spec.js
@@ -0,0 +1,8 @@
+import Vue from 'vue'
+import Login from '@/components/Login'
+
+describe('Login.vue', () => {
+  it('is doing nothing but ensure that we can test', () => {
+
+  })
+})
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 9fa697bc8f22d8a02a6f90c1143b762e91d1b02a..d417ec11d84acfaefbada048d993b002dae8a9c2 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -1388,6 +1388,13 @@ concat-stream@1.6.0, concat-stream@^1.5.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
+config-chain@~1.1.5:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2"
+  dependencies:
+    ini "^1.3.4"
+    proto-list "~1.2.1"
+
 connect-history-api-fallback@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.4.0.tgz#3db24f973f4b923b0e82f619ce0df02411ca623d"
@@ -2016,7 +2023,7 @@ es6-map@^0.1.3:
     es6-symbol "~3.1.1"
     event-emitter "~0.3.5"
 
-es6-promise@^4.0.3:
+es6-promise@^4.0.3, es6-promise@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a"
 
@@ -3039,7 +3046,7 @@ inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
 
-ini@~1.3.0:
+ini@^1.3.4, ini@~1.3.0:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
 
@@ -4039,7 +4046,7 @@ node-pre-gyp@^0.6.36:
     tar "^2.2.1"
     tar-pack "^3.4.0"
 
-nopt@3.x:
+nopt@3.x, nopt@~3.0.1:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
   dependencies:
@@ -4770,6 +4777,10 @@ progress@^1.1.8:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
 
+proto-list@~1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+
 proxy-addr@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -5912,6 +5923,14 @@ void-elements@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
 
+vue-beautify@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/vue-beautify/-/vue-beautify-1.1.3.tgz#69d193b641db0047e26378858bb759754ee53692"
+  dependencies:
+    config-chain "~1.1.5"
+    mkdirp "~0.5.0"
+    nopt "~3.0.1"
+
 vue-functional-data-merge@^1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-1.0.6.tgz#cde4f0cf3f251f7f0196341156d2c936cca63d40"