Skip to content
Snippets Groups Projects
Commit 4cb4134e authored by Jan Maximilian Michal's avatar Jan Maximilian Michal
Browse files

Added basic logging configuration. Enabled karma tests. Reverted changes

to User model

- It is now possible to run tests in the frontend.
- The backend authentication uses AbstractUser instead of
AbsstractBaseuser
parent a7403bc5
No related branches found
No related tags found
2 merge requests!15Refactor,!13Student page
from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import Group
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from core.models import (Feedback, Reviewer, Student, Submission, from core.models import (Feedback, Reviewer, Student, Submission,
SubmissionType, Test, Tutor, UserAccount) SubmissionType, Test, Tutor, UserAccount, ExamType)
# Stuff we needwant
class UserCreationForm(forms.ModelForm): admin.site.register(UserAccount)
"""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)
admin.site.register(SubmissionType) admin.site.register(SubmissionType)
admin.site.register(Feedback) admin.site.register(Feedback)
admin.site.register(Test) admin.site.register(Test)
admin.site.register(ExamType)
admin.site.register(Submission) admin.site.register(Submission)
admin.site.register(Reviewer) admin.site.register(Reviewer)
admin.site.register(Student) admin.site.register(Student)
admin.site.register(Tutor) admin.site.register(Tutor)
# ... and stuff we don't needwant
admin.site.unregister(Group)
# -*- 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'),
),
]
...@@ -11,7 +11,7 @@ from random import randrange, sample ...@@ -11,7 +11,7 @@ from random import randrange, sample
from string import ascii_lowercase from string import ascii_lowercase
from django.contrib.auth import get_user_model 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 import models
from django.db.models import Value as V from django.db.models import Value as V
from django.db.models import (BooleanField, Case, Count, F, IntegerField, Q, from django.db.models import (BooleanField, Case, Count, F, IntegerField, Q,
...@@ -150,64 +150,16 @@ class SubmissionType(models.Model): ...@@ -150,64 +150,16 @@ class SubmissionType(models.Model):
).order_by('name') ).order_by('name')
class UserAccountManager(BaseUserManager): class UserAccount(AbstractUser):
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):
""" """
An abstract base class implementing a fully featured User model with An abstract base class implementing a fully featured User model with
admin-compliant permissions. admin-compliant permissions.
Username and password are required. Other fields are optional. 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) 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_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): def get_associated_user(self):
""" Returns the user type that is associated with this user obj """ """ Returns the user type that is associated with this user obj """
...@@ -216,12 +168,6 @@ class UserAccount(AbstractBaseUser): ...@@ -216,12 +168,6 @@ class UserAccount(AbstractBaseUser):
(hasattr(self, 'reviewer') and self.reviewer) or \ (hasattr(self, 'reviewer') and self.reviewer) or \
(hasattr(self, 'tutor') and self.tutor) (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): class Tutor(models.Model):
user = models.OneToOneField( user = models.OneToOneField(
...@@ -535,10 +481,10 @@ class Feedback(models.Model): ...@@ -535,10 +481,10 @@ class Feedback(models.Model):
ACCEPTED, ACCEPTED,
) = range(4) # this order matters ) = range(4) # this order matters
STATUS = ( STATUS = (
(EDITABLE, 'editable'), (EDITABLE, 'editable'),
(OPEN, 'request reassignment'), (OPEN, 'request reassignment'),
(NEEDS_REVIEW, 'request review'), (NEEDS_REVIEW, 'request review'),
(ACCEPTED, 'accepted'), (ACCEPTED, 'accepted'),
) )
status = models.IntegerField( status = models.IntegerField(
choices=STATUS, choices=STATUS,
...@@ -554,11 +500,11 @@ class Feedback(models.Model): ...@@ -554,11 +500,11 @@ class Feedback(models.Model):
MANUAL, MANUAL,
) = range(5) ) = range(5)
ORIGIN = ( ORIGIN = (
(WAS_EMPTY, 'was empty'), (WAS_EMPTY, 'was empty'),
(FAILED_UNIT_TESTS, 'passed unittests'), (FAILED_UNIT_TESTS, 'passed unittests'),
(DID_NOT_COMPILE, 'did not compile'), (DID_NOT_COMPILE, 'did not compile'),
(COULD_NOT_LINK, 'could not link'), (COULD_NOT_LINK, 'could not link'),
(MANUAL, 'created by a human. yak!'), (MANUAL, 'created by a human. yak!'),
) )
origin = models.IntegerField( origin = models.IntegerField(
choices=ORIGIN, choices=ORIGIN,
......
...@@ -2,7 +2,6 @@ from rest_framework import serializers ...@@ -2,7 +2,6 @@ from rest_framework import serializers
from core.models import ExamType, Feedback, Student, Submission from core.models import ExamType, Feedback, Student, Submission
class ExamSerializer(serializers.ModelSerializer): class ExamSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
import logging
from rest_framework.generics import RetrieveAPIView from rest_framework.generics import RetrieveAPIView
from core.permissions import IsStudent from core.permissions import IsStudent
from core.serializers import (FeedbackSerializer, StudentSerializer, from core.serializers import (FeedbackSerializer, StudentSerializer,
SubmissionSerializer) SubmissionSerializer)
log = logging.getLogger(__name__)
class StudentApiView(RetrieveAPIView): class StudentApiView(RetrieveAPIView):
permission_classes = (IsStudent,) permission_classes = (IsStudent,)
def get_object(self): def get_object(self):
log.debug("Serializing student of user '%s'", self.request.user.username)
return self.request.user.student return self.request.user.student
serializer_class = StudentSerializer serializer_class = StudentSerializer
......
...@@ -159,3 +159,52 @@ REST_FRAMEWORK = { ...@@ -159,3 +159,52 @@ REST_FRAMEWORK = {
JWT_AUTH = { JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=600), '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',
}
}
}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
"start": "npm run dev", "start": "npm run dev",
"build": "node build/build.js", "build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", "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" "lint": "eslint --ext .js,.vue src test/unit/specs"
}, },
"dependencies": { "dependencies": {
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"bootstrap": "4.0.0-beta.2", "bootstrap": "4.0.0-beta.2",
"bootstrap-vue": "^1.0.0", "bootstrap-vue": "^1.0.0",
"vue": "^2.5.2", "vue": "^2.5.2",
"vue-beautify": "^1.1.3",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vuex": "^3.0.1" "vuex": "^3.0.1"
}, },
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.1", "cross-env": "^5.0.1",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
"es6-promise": "^4.1.1",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-config-standard": "^10.2.1", "eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0", "eslint-friendly-formatter": "^3.0.0",
......
import Vue from 'vue' import Vue from 'vue'
import 'es6-promise/auto'
Vue.config.productionTip = false Vue.config.productionTip = false
......
...@@ -7,10 +7,6 @@ var webpackConfig = require('../../build/webpack.test.conf') ...@@ -7,10 +7,6 @@ var webpackConfig = require('../../build/webpack.test.conf')
module.exports = function (config) { module.exports = function (config) {
config.set({ 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'], browsers: ['PhantomJS'],
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
reporters: ['spec', 'coverage'], reporters: ['spec', 'coverage'],
......
import Vue from 'vue' import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld' import Login from '@/components/Login'
describe('HelloWorld.vue', () => { describe('Login.vue', () => {
it('should render correct contents', () => { it('is doing nothing but ensure that we can test', () => {
const Constructor = Vue.extend(HelloWorld) data = {"submissions": [
const vm = new Constructor().$mount() {
expect(vm.$el.querySelector('.hello h1').textContent) "type": "Aufgabe 01",
.to.equal('Welcome to Your Vue.js App') "text": "Hallo, was geht denn da?",
"feedback": null,
"score": null,
"full_score": 12
}
]}
}) })
}) })
import Vue from 'vue'
import Login from '@/components/Login'
describe('Login.vue', () => {
it('is doing nothing but ensure that we can test', () => {
})
})
...@@ -1388,6 +1388,13 @@ concat-stream@1.6.0, concat-stream@^1.5.2: ...@@ -1388,6 +1388,13 @@ concat-stream@1.6.0, concat-stream@^1.5.2:
readable-stream "^2.2.2" readable-stream "^2.2.2"
typedarray "^0.0.6" 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: connect-history-api-fallback@^1.3.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.4.0.tgz#3db24f973f4b923b0e82f619ce0df02411ca623d" 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: ...@@ -2016,7 +2023,7 @@ es6-map@^0.1.3:
es6-symbol "~3.1.1" es6-symbol "~3.1.1"
event-emitter "~0.3.5" event-emitter "~0.3.5"
es6-promise@^4.0.3: es6-promise@^4.0.3, es6-promise@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a"
...@@ -3039,7 +3046,7 @@ inherits@2.0.1: ...@@ -3039,7 +3046,7 @@ inherits@2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 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" version "1.3.4"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
...@@ -4039,7 +4046,7 @@ node-pre-gyp@^0.6.36: ...@@ -4039,7 +4046,7 @@ node-pre-gyp@^0.6.36:
tar "^2.2.1" tar "^2.2.1"
tar-pack "^3.4.0" tar-pack "^3.4.0"
nopt@3.x: nopt@3.x, nopt@~3.0.1:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
dependencies: dependencies:
...@@ -4770,6 +4777,10 @@ progress@^1.1.8: ...@@ -4770,6 +4777,10 @@ progress@^1.1.8:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" 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: proxy-addr@~2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
...@@ -5912,6 +5923,14 @@ void-elements@^2.0.0: ...@@ -5912,6 +5923,14 @@ void-elements@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" 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: vue-functional-data-merge@^1.0.6:
version "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" resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-1.0.6.tgz#cde4f0cf3f251f7f0196341156d2c936cca63d40"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment