diff --git a/backend/.gitignore b/backend/.gitignore index 0321231027fe3a867d47cd620a82adc7d1ce4832..86ca4a4842208f80247e2f5f30648fa73444dabe 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -7,6 +7,7 @@ __pycache__ MANIFEST .coverage cache/ +.mypy_cache/ # Django specific dist/ diff --git a/backend/core/migrations/0001_initial.py b/backend/core/migrations/0001_initial.py index 7fd6cdada7a80ed66d0adb09f55bb01c58a93272..5f840961f0accd6d68d03bb079d421e7f0322bcd 100644 --- a/backend/core/migrations/0001_initial.py +++ b/backend/core/migrations/0001_initial.py @@ -2,6 +2,8 @@ # Generated by Django 1.11.7 on 2017-11-04 19:10 from __future__ import unicode_literals +from typing import List, Text + import django.db.models.deletion from django.conf import settings from django.db import migrations, models @@ -13,8 +15,7 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies: List[Text] = [] operations = [ migrations.CreateModel( diff --git a/backend/core/models.py b/backend/core/models.py index c76e76e29491540a64d478f53c7447393e507a3a..76b024601d0fb85b549519d734f700d63d1ad2b6 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -6,6 +6,8 @@ See docstring of the individual models for information on the setup of the database. ''' +from typing import Union, Dict + from collections import OrderedDict from random import randrange, sample from string import ascii_lowercase @@ -15,7 +17,7 @@ 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, - Sum, When) + QuerySet, Sum, When) from django.db.models.functions import Coalesce @@ -28,7 +30,7 @@ def random_matrikel_no() -> str: return str(10_000_000 + randrange(90_000_000)) -def get_annotated_tutor_list(): +def get_annotated_tutor_list() -> QuerySet: """All tutor accounts are annotate with a field that includes the number of feedback that tutor has collaborated in. @@ -105,7 +107,7 @@ class SubmissionType(models.Model): verbose_name_plural = "SubmissionType Set" @classmethod - def get_annotated_feedback_count(cls): + def get_annotated_feedback_count(cls) -> QuerySet: """ Annotates submission lists with counts The following fields are annotated: @@ -146,7 +148,7 @@ class UserAccount(AbstractUser): fullname = models.CharField('full name', max_length=70, blank=True) is_admin = models.BooleanField(default=False) - def get_associated_user(self): + def get_associated_user(self) -> models.Model: """ Returns the user type that is associated with this user obj """ return \ (hasattr(self, 'student') and self.student) or \ @@ -162,6 +164,7 @@ class Tutor(models.Model): def get_feedback_count(self) -> int: return self.feedback_list.count() + class Reviewer(models.Model): user = models.OneToOneField( get_user_model(), unique=True, @@ -197,7 +200,7 @@ class Student(models.Model): get_user_model(), unique=True, on_delete=models.CASCADE, related_name='student') - def score_per_submission(self): + def score_per_submission(self) -> Dict[str, int]: """ TODO: get rid of it and use an annotation. Returns: @@ -214,7 +217,7 @@ class Student(models.Model): }) @classmethod - def get_overall_score_annotated_submission_list(cls): + def get_overall_score_annotated_submission_list(cls) -> QuerySet: """Can be used to quickly annotate a user with the necessary information on the overall score of a student and if he does not need any more correction. @@ -331,7 +334,7 @@ class Submission(models.Model): ) @classmethod - def assign_tutor(cls, tutor, slug=None) -> bool: + def assign_tutor(cls, tutor: Tutor, slug: str=None) -> bool: """Assigns a tutor to a submission A submission is not assigned to the specified tutor in the case @@ -493,14 +496,14 @@ class Feedback(models.Model): def __str__(self) -> str: return 'Feedback for {}'.format(self.of_submission) - def is_full_score(self): + def is_full_score(self) -> bool: return self.of_submission.type.full_score == self.score - def get_full_score(self): + def get_full_score(self) -> int: return self.of_submission.type.full_score @classmethod - def get_open_feedback(cls, user): + def get_open_feedback(cls, user: Union[Tutor, Reviewer]) -> QuerySet: """For a user, returns the feedback that is up for reassignment that does not belong to the user. @@ -523,7 +526,7 @@ class Feedback(models.Model): ) @classmethod - def tutor_unfinished_feedback(cls, user): + def tutor_unfinished_feedback(cls, user: Union[Tutor, Reviewer]): """Gets only the feedback that is assigned and not accepted. A tutor should have only one feedback assigned that is not accepted @@ -541,7 +544,7 @@ class Feedback(models.Model): ) return tutor_feedback[0] if tutor_feedback else None - def tutor_assigned_feedback(cls, user): + def tutor_assigned_feedback(cls, user: Union[Tutor, Reviewer]): """Gets all feedback that is assigned to the tutor including all status cases. @@ -557,7 +560,7 @@ class Feedback(models.Model): tutor_feedback = cls.objects.filter(of_tutor=user) return tutor_feedback - def finalize_feedback(self, user): + def finalize_feedback(self, user: Union[Tutor, Reviewer]): """Used to mark feedback as accepted (reviewed). Parameters @@ -569,17 +572,7 @@ class Feedback(models.Model): self.of_reviewer = user self.save() - def unfinalize_feedback(self): - """Used to mark feedback as accepted (reviewed) - - This makes it uneditable by the tutor. - """ - - self.origin = Feedback.MANUAL - self.of_reviewer = None - self.save() - - def reassign_to_tutor(self, user): + def reassign_to_tutor(self, user: Union[Tutor, Reviewer]): """When a tutor does not want to correct some task they can pass it along to another tutor who will accept the request. diff --git a/backend/core/permissions.py b/backend/core/permissions.py index 65c83d6b78777fb276eb9a9ae60a43e269b3f59b..29b60731037a3d58727b44826b189de4b8f6217e 100644 --- a/backend/core/permissions.py +++ b/backend/core/permissions.py @@ -1,13 +1,15 @@ from rest_framework import permissions from core.models import Reviewer, Student, Tutor +from django.http import HttpRequest +from django.views import View class IsUserGenericPermission(permissions.BasePermission): """ Generic class that encapsulates how to identify someone as a member of a user Group """ - def has_permission(self, request, view): + def has_permission(self, request: HttpRequest, view: View) -> bool: """ required by BasePermission. Check if user is instance of model""" assert self.model is not None, ( "'%s' has to include a `model` attribute" diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 60c095f5c89a0b592f3a67ec5d41b84e3b786faf..afccb1bb6212bc3edd35c18680e022f464fb028e 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -51,7 +51,7 @@ class TutorSerializer(serializers.ModelSerializer): feedback_count = serializers.IntegerField(source='get_feedback_count', read_only=True) - def create(self, validated_data): + def create(self, validated_data) -> Tutor: log.info("Crating tutor from data %s", validated_data) return user_factory.make_tutor( username=validated_data['user']['username']) diff --git a/backend/core/views.py b/backend/core/views.py index bffb8caf2f16d2e0c65ec5292b318155a9f98c66..4470a4ab59e360f0ef30fa6c37d1f850e4cf6aac 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -5,7 +5,7 @@ import logging from rest_framework import generics -from core.models import ExamType, Tutor +from core.models import ExamType, Tutor, Student from core.permissions import IsReviewer, IsStudent from core.serializers import ExamSerializer, StudentSerializer, TutorSerializer @@ -17,7 +17,7 @@ class StudentApiView(generics.RetrieveAPIView): permission_classes = (IsStudent,) serializer_class = StudentSerializer - def get_object(self): + def get_object(self) -> Student: """ The object in question is the student associated with the requests user. Since the permission IsStudent is satisfied the member exists """ return self.request.user.student