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

Merge branch 'student-page' into refactor

parents a5acc134 cc434696
Branches
Tags
1 merge request!15Refactor
Pipeline #
Showing
with 390 additions and 222 deletions
......@@ -34,6 +34,12 @@ test_pylint:
- cd backend/
- pylint core || exit 0
test_prospector:
<<: *test_definition
script:
- cd backend/
- prospector --uses django || exit 0
.staging_template: &staging_definition
stage: staging
......
......@@ -2,7 +2,7 @@
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS, migrations, static, env, docs, manage.py, tests
ignore=CVS, migrations, static, env, docs, manage.py
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
......@@ -13,7 +13,7 @@ ignore-patterns=
#init-hook=
# Use multiple processes to speed up Pylint.
jobs=2
jobs=4
# Pickle collected data for later comparisons.
persistent=yes
......@@ -26,6 +26,8 @@ suggestion-mode=yes
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
[MESSAGES CONTROL]
......
......@@ -31,12 +31,12 @@ install:
yarn
test:
python manage.py run test
python manage.py test
coverage:
coverage run manage.py test
coverage report
db:
docker run -rm --name $(DB_NAME) -p 5432:5432 postgres:9.5
docker run --rm --name $(DB_NAME) -p 5432:5432 postgres:9.5
......@@ -6,81 +6,16 @@ from django.contrib.auth.forms import ReadOnlyPasswordHashField
from core.models import (Feedback, Reviewer, Student, Submission,
SubmissionType, Test, Tutor, UserAccount)
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 = ('username',)
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 = ('username', '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': ('username', 'password', 'is_admin', 'is_active')}),
)
# 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': ('username', '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)
# -*- 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
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,
......
from rest_framework import serializers
from core.models import Student, Submission, Feedback, ExamType
from core.models import Feedback, Student, Submission
from rest_framework import serializers
from core.models import Student, Submission, Feedback, ExamType
from core.models import ExamType, Feedback, Student, Submission
class ExamSerializer(serializers.ModelSerializer):
class Meta:
model = ExamType
fields = ('module_reference', 'total_score', 'pass_score', 'pass_only',)
fields = ('module_reference', 'total_score',
'pass_score', 'pass_only',)
class FeedbackSerializer(serializers.ModelSerializer):
class Meta:
model = Feedback
fields = ('text', 'score')
......
......@@ -54,6 +54,7 @@ class FactoryTestCase(TestCase):
def test_can_create_student(self):
self.assertIn(self.factory.make_student(), Student.objects.all())
class AccountsTestCase(TestCase):
factory = GradyUserFactory()
......
from core.serializers import SubmissionSerializer, StudentSerializer, FeedbackSerializer
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
......
......@@ -10,8 +10,8 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
import datetime
import os
from django.contrib.messages import constants as messages
......@@ -160,3 +160,51 @@ 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',
}
}
}
......@@ -7,5 +7,5 @@ gunicorn~=19.7.0
psycopg2~=2.7.1
xlrd~=1.0.0
pytest-cov~=2.5.1
pylint~=1.7.4
prospector~=0.12.7
django-cors-headers~=2.1.0
......@@ -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": {
......@@ -36,6 +36,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",
......
<template>
<div class="row my-2 justify-content-center">
<b-table hover :items="submissions" :fields="fields"></b-table>
<div class="alert alert-info">You reached <b>{{ sumScore }}</b> of <b>{{ sumFullScore }}</b> possible points( {{ pointRatio }}% ).</div>
<div class="alert alert-info">
You reached <b>{{ sumScore }}</b> of <b>{{ sumFullScore }}</b> possible points( {{ pointRatio }}% ).
</div>
</div>
</template>
......
import Vue from 'vue'
import 'es6-promise/auto'
Vue.config.productionTip = false
......
......@@ -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'],
......
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'
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')
})
})
import Vue from 'vue'
import SubmissionList from '@/components/student/SubmissionList'
describe('SubmissionList.vue', () => {
it('tests the SubmissionList for students', () => {
const data = [{
'type': 'Aufgabe 01',
'text': 'I dont know the answer.',
'feedback': 'I am very disappointed.',
'score': 5,
'full_score': 14
},
{
'type': 'Aufgabe 01',
'text': 'A very good solution, indeed',
'feedback': 'I am still very disappointed.',
'score': 7,
'full_score': 10
}]
const Constructor = Vue.extend(SubmissionList)
const comp = new Constructor({
propsData: {
// Props are passed in "propsData".
submissions: data
}
}).$mount()
expect(comp.sumScore)
.to.equal(12)
expect(comp.sumFullScore)
.to.equal(24)
expect(comp.pointRatio)
.to.equal('50.00')
})
})
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment