From 6db1cf0516f0d96b1986198917743f6afc1af2b6 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Fri, 5 Jan 2018 13:42:26 +0100 Subject: [PATCH] Removed tutor and reviewer model --- core/admin.py | 8 +-- core/migrations/0007_auto_20180105_1136.py | 41 +++++++++++ core/models.py | 61 ++++++++-------- core/permissions.py | 26 ++++--- core/serializers.py | 21 +++--- core/tests/test_access_rights.py | 24 +++---- core/tests/test_examlist.py | 2 +- core/tests/test_factory_and_feedback.py | 46 +++++------- core/tests/test_functional_views.py | 6 +- core/tests/test_student_page.py | 2 +- core/tests/test_student_reviewer_viewset.py | 4 +- core/tests/test_submissiontypeview.py | 2 +- .../test_subscription_assignment_service.py | 8 ++- core/tests/test_tutor_api_endpoints.py | 23 +++--- core/urls.py | 5 +- core/views.py | 33 ++++----- delbert.py | 6 +- util/factories.py | 71 +++++++++---------- util/importer.py | 4 +- 19 files changed, 208 insertions(+), 185 deletions(-) create mode 100644 core/migrations/0007_auto_20180105_1136.py diff --git a/core/admin.py b/core/admin.py index a2e9f295..79001a88 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin from django.contrib.auth.models import Group -from core.models import (ExamType, Feedback, Reviewer, Student, Submission, - SubmissionType, Test, Tutor, UserAccount) +from core.models import (ExamType, Feedback, StudentInfo, Submission, + SubmissionType, Test, UserAccount) # Stuff we needwant admin.site.register(UserAccount) @@ -11,9 +11,7 @@ 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) +admin.site.register(StudentInfo) # ... and stuff we don't needwant admin.site.unregister(Group) diff --git a/core/migrations/0007_auto_20180105_1136.py b/core/migrations/0007_auto_20180105_1136.py new file mode 100644 index 00000000..2b3aa7ee --- /dev/null +++ b/core/migrations/0007_auto_20180105_1136.py @@ -0,0 +1,41 @@ +# Generated by Django 2.0 on 2018-01-05 11:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_auto_20180104_2001'), + ] + + operations = [ + migrations.RenameModel( + old_name='Student', + new_name='StudentInfo', + ), + migrations.RemoveField( + model_name='reviewer', + name='user', + ), + migrations.RemoveField( + model_name='tutor', + name='user', + ), + migrations.RemoveField( + model_name='feedback', + name='of_reviewer', + ), + migrations.AddField( + model_name='useraccount', + name='role', + field=models.CharField(choices=[('Student', 'student'), ('Tutor', 'tutor'), ('Reviewer', 'reviewer')], default='Student', max_length=50), + preserve_default=False, + ), + migrations.DeleteModel( + name='Reviewer', + ), + migrations.DeleteModel( + name='Tutor', + ), + ] diff --git a/core/models.py b/core/models.py index 1f7d1927..f2268ed9 100644 --- a/core/models.py +++ b/core/models.py @@ -149,43 +149,48 @@ class UserAccount(AbstractUser): fullname = models.CharField('full name', max_length=70, blank=True) is_admin = models.BooleanField(default=False) - 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 \ - (hasattr(self, 'reviewer') and self.reviewer) or \ - (hasattr(self, 'tutor') and self.tutor) + STUDENT = 'Student' + TUTOR = 'Tutor' + REVIEWER = 'Reviewer' + + ROLE_CHOICES = ( + (STUDENT, 'student'), + (TUTOR, 'tutor'), + (REVIEWER, 'reviewer') + ) - def is_reviewer(self): - return hasattr(self, 'reviewer') - - def is_tutor(self): - return hasattr(self, 'tutor') + role = models.CharField(max_length=50, choices=ROLE_CHOICES) def is_student(self): - return hasattr(self, 'student') + return self.role == 'Student' + def is_tutor(self): + return self.role == 'Tutor' -class Tutor(models.Model): - user = models.OneToOneField(get_user_model(), - on_delete=models.CASCADE, - related_name='tutor') + def is_reviewer(self): + return self.role == 'Reviewer' def get_feedback_count(self) -> int: return self.feedback_list.count() + @classmethod + def get_students(cls): + return cls.objects.filter(role=cls.STUDENT) -class Reviewer(models.Model): - user = models.OneToOneField(get_user_model(), - on_delete=models.CASCADE, - related_name='reviewer') + @classmethod + def get_tutors(cls): + return cls.objects.filter(role=cls.TUTOR) + + @classmethod + def get_reviewers(cls): + return cls.objects.filter(role=cls.REVIEWER) -class Student(models.Model): +class StudentInfo(models.Model): """ - The student model includes all information of a student, that we got from - the E-Learning output, along with some useful classmethods that provide - specially annotated QuerySets. + The StudentInfo model includes all information of a student, that we got + from the E-Learning output, along with some useful classmethods that + provide specially annotated QuerySets. Information like email (if given), and the username are stored in the associated user model. @@ -331,7 +336,7 @@ class Submission(models.Model): on_delete=models.PROTECT, related_name='submissions') student = models.ForeignKey( - Student, + StudentInfo, on_delete=models.CASCADE, related_name='submissions') @@ -389,12 +394,6 @@ class Feedback(models.Model): related_name='feedback_list', blank=True, null=True) - of_reviewer = models.ForeignKey( - Reviewer, - on_delete=models.SET_NULL, - related_name='reviewed_submissions', - blank=True, - null=True) # how was this feedback created ( diff --git a/core/permissions.py b/core/permissions.py index 5466fa23..e88aed69 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -4,26 +4,24 @@ from django.http import HttpRequest from django.views import View from rest_framework import permissions -from core.models import Reviewer, Student, Tutor log = logging.getLogger(__name__) -class IsUserGenericPermission(permissions.BasePermission): +class IsUserRoleGenericPermission(permissions.BasePermission): """ Generic class that encapsulates how to identify someone as a member of a user Group """ def has_permission(self, request: HttpRequest, view: View) -> bool: """ required by BasePermission. Check if user is instance of any of the models provided in class' models attribute """ - assert self.models is not None, ( - "'%s' has to include a `models` attribute" + assert self.roles is not None, ( + "'%s' has to include a `roles` attribute" % self.__class__.__name__ ) user = request.user - is_authorized = user.is_authenticated and any(isinstance( - user.get_associated_user(), models) for models in self.models) + is_authorized = user.is_authenticated and user.role in self.roles if not is_authorized: log.warn('User "%s" has no permission to view %s', @@ -32,21 +30,21 @@ class IsUserGenericPermission(permissions.BasePermission): return is_authorized -class IsStudent(IsUserGenericPermission): +class IsStudent(IsUserRoleGenericPermission): """ Has student permissions """ - models = (Student,) + roles = ('Student', ) -class IsReviewer(IsUserGenericPermission): +class IsReviewer(IsUserRoleGenericPermission): """ Has reviewer permissions """ - models = (Reviewer,) + roles = ('Reviewer', ) -class IsTutor(IsUserGenericPermission): +class IsTutor(IsUserRoleGenericPermission): """ Has tutor permissions """ - models = (Tutor,) + roles = ('Tutor', ) -class IsTutorOrReviewer(IsUserGenericPermission): +class IsTutorOrReviewer(IsUserRoleGenericPermission): """ Has tutor or reviewer permissions """ - models = (Tutor, Reviewer,) + roles = ('Tutor', 'Reviewer') diff --git a/core/serializers.py b/core/serializers.py index cd017afe..18b7f142 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -6,8 +6,10 @@ from django.core.exceptions import ObjectDoesNotExist from drf_dynamic_fields import DynamicFieldsMixin from rest_framework import serializers -from core.models import (ExamType, Feedback, GeneralTaskSubscription, Student, - Submission, SubmissionType, Tutor, +from core import models +from core.models import (ExamType, Feedback, GeneralTaskSubscription, + StudentInfo, + Submission, SubmissionType, TutorSubmissionAssignment) from util.factories import GradyUserFactory @@ -94,14 +96,14 @@ class SubmissionSerializer(DynamicFieldsModelSerializer): 'feedback', 'score', 'full_score') -class StudentSerializer(DynamicFieldsModelSerializer): +class StudentInfoSerializer(DynamicFieldsModelSerializer): name = serializers.ReadOnlyField(source='user.fullname') user = serializers.ReadOnlyField(source='user.username') exam = ExamSerializer() submissions = SubmissionSerializer(many=True) class Meta: - model = Student + model = StudentInfo fields = ('name', 'user', 'exam', 'submissions') @@ -115,29 +117,28 @@ class SubmissionNoTextFieldsSerializer(DynamicFieldsModelSerializer): fields = ('type', 'score', 'full_score') -class StudentSerializerForListView(DynamicFieldsModelSerializer): +class StudentInfoSerializerForListView(DynamicFieldsModelSerializer): name = serializers.ReadOnlyField(source='user.fullname') user = serializers.ReadOnlyField(source='user.username') exam = serializers.ReadOnlyField(source='exam.module_reference') submissions = SubmissionNoTextFieldsSerializer(many=True) class Meta: - model = Student + model = StudentInfo fields = ('name', 'user', 'exam', 'submissions') class TutorSerializer(DynamicFieldsModelSerializer): - username = serializers.CharField(source='user.username') feedback_count = serializers.IntegerField(source='get_feedback_count', read_only=True) - def create(self, validated_data) -> Tutor: + def create(self, validated_data) -> models.UserAccount: log.info("Crating tutor from data %s", validated_data) return user_factory.make_tutor( - username=validated_data['user']['username']) + username=validated_data['username']) class Meta: - model = Tutor + model = models.UserAccount fields = ('username', 'feedback_count') diff --git a/core/tests/test_access_rights.py b/core/tests/test_access_rights.py index 9c22248b..8a636dbf 100644 --- a/core/tests/test_access_rights.py +++ b/core/tests/test_access_rights.py @@ -29,17 +29,17 @@ class AccessRightsOfStudentAPIViewTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_tutor_has_no_access(self): - force_authenticate(self.request, user=self.tutor.user) + force_authenticate(self.request, user=self.tutor) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_reviewer_has_no_access(self): - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_student_is_authorized(self): - force_authenticate(self.request, user=self.student.user) + force_authenticate(self.request, user=self.student) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -64,17 +64,17 @@ class AccessRightsOfTutorAPIViewTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_student_has_no_access(self): - force_authenticate(self.request, user=self.student.user) + force_authenticate(self.request, user=self.student) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_tutor_has_no_access(self): - force_authenticate(self.request, user=self.tutor.user) + force_authenticate(self.request, user=self.tutor) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_reviewer_has_access(self): - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -100,17 +100,17 @@ class AccessRightsOfStudentReviewerAPIViewTest(APITestCase): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_student_has_no_access(self): - force_authenticate(self.request, user=self.student.user) + force_authenticate(self.request, user=self.student) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_tutor_has_no_access(self): - force_authenticate(self.request, user=self.tutor.user) + force_authenticate(self.request, user=self.tutor) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_reviewer_has_access(self): - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -134,16 +134,16 @@ class AccessRightsOfExamTypeAPIViewTest(APITestCase): self.view = ExamApiViewSet.as_view({'get': 'list'}) def test_student_has_no_access(self): - force_authenticate(self.request, user=self.student.user) + force_authenticate(self.request, user=self.student) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_tutor_has_no_access(self): - force_authenticate(self.request, user=self.tutor.user) + force_authenticate(self.request, user=self.tutor) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_reviewer_has_access(self): - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) response = self.view(self.request) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/core/tests/test_examlist.py b/core/tests/test_examlist.py index f1e99b2d..b5be17cc 100644 --- a/core/tests/test_examlist.py +++ b/core/tests/test_examlist.py @@ -24,7 +24,7 @@ class ExamListTest(APITestCase): total_score=90, pass_score=45) force_authenticate(self.request, - self.user_factory.make_reviewer().user) + self.user_factory.make_reviewer()) self.view = ExamApiViewSet.as_view({'get': 'list'}) self.response = self.view(self.request) diff --git a/core/tests/test_factory_and_feedback.py b/core/tests/test_factory_and_feedback.py index 42ed4a4e..5b582890 100644 --- a/core/tests/test_factory_and_feedback.py +++ b/core/tests/test_factory_and_feedback.py @@ -1,6 +1,7 @@ from django.test import TestCase -from core.models import Reviewer, Student, Tutor +from core import models +from core.models import StudentInfo from util.factories import GradyUserFactory @@ -10,39 +11,28 @@ class FactoryTestCase(TestCase): def test_make_student(self): - student = self.factory.make_student() + user = self.factory.make_student() - self.assertEqual(Student.objects.count(), 1) - self.assertEqual(student.exam, None) - self.assertEqual(len(str(student.matrikel_no)), 8) + self.assertEqual(StudentInfo.objects.count(), 1) + self.assertEqual(user.student.exam, None) + self.assertEqual(len(str(user.student.matrikel_no)), 8) def test_can_create_reviewer(self): - self.assertTrue(isinstance(self.factory.make_reviewer(), Reviewer)) + self.assertTrue(isinstance(self.factory.make_reviewer(), + models.UserAccount)) def test_reviewer_appears_in_query_set(self): - self.assertIn(self.factory.make_reviewer(), Reviewer.objects.all()) + self.assertIn(self.factory.make_reviewer(), + models.UserAccount.objects.all()) def test_can_create_tutor(self): - self.assertIn(self.factory.make_tutor(), Tutor.objects.all()) + self.assertIn(self.factory.make_tutor(), + models.UserAccount.objects.all()) - def test_can_create_student(self): - self.assertIn(self.factory.make_student(), Student.objects.all()) + def test_can_create_student_user(self): + self.assertIn(self.factory.make_student(), + models.UserAccount.objects.all()) - -class AccountsTestCase(TestCase): - - factory = GradyUserFactory() - - def _test_user_obj_returns_correct_type(self, maker): - model = maker() - user = model.user - self.assertEqual(user.get_associated_user(), model) - - def test_user_obj_returns_correct_type_student(self): - self._test_user_obj_returns_correct_type(self.factory.make_student) - - def test_user_obj_returns_correct_type_tutor(self): - self._test_user_obj_returns_correct_type(self.factory.make_tutor) - - def test_user_obj_returns_correct_type_reviewer(self): - self._test_user_obj_returns_correct_type(self.factory.make_reviewer) + def test_can_create_student_info(self): + self.assertIn(self.factory.make_student().student, + StudentInfo.objects.all()) diff --git a/core/tests/test_functional_views.py b/core/tests/test_functional_views.py index 5a99c8b9..7763b5c6 100644 --- a/core/tests/test_functional_views.py +++ b/core/tests/test_functional_views.py @@ -19,16 +19,16 @@ class GetUserRoleTest(APITestCase): self.request = self.factory.get(reverse('user-role')) def test_get_user_model_returns_student(self): - force_authenticate(self.request, user=self.student.user) + force_authenticate(self.request, user=self.student) response = get_user_role(self.request) self.assertEqual(response.data['role'], 'Student') def test_get_user_model_returns_tutor(self): - force_authenticate(self.request, user=self.tutor.user) + force_authenticate(self.request, user=self.tutor) response = get_user_role(self.request) self.assertEqual(response.data['role'], 'Tutor') def test_get_user_model_returns_reviewer(self): - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) response = get_user_role(self.request) self.assertEqual(response.data['role'], 'Reviewer') diff --git a/core/tests/test_student_page.py b/core/tests/test_student_page.py index 8bd9f349..a3fa4f7e 100644 --- a/core/tests/test_student_page.py +++ b/core/tests/test_student_page.py @@ -49,7 +49,7 @@ class StudentPageTests(APITestCase): }] }) - self.student = self.test_data['students'][0] + self.student = self.test_data['students'][0].student self.tutor = self.test_data['tutors'][0] self.reviewer = self.test_data['reviewers'][0] self.submission = self.test_data['submissions'][0] diff --git a/core/tests/test_student_reviewer_viewset.py b/core/tests/test_student_reviewer_viewset.py index d486641c..12500539 100644 --- a/core/tests/test_student_reviewer_viewset.py +++ b/core/tests/test_student_reviewer_viewset.py @@ -48,14 +48,14 @@ class StudentPageTests(APITestCase): }] }) - self.student = self.test_data['students'][0] + self.student = self.test_data['students'][0].student self.reviewer = self.test_data['reviewers'][0] self.submission = self.test_data['submissions'][0] self.request = self.factory.get(reverse('student-list')) self.view = StudentReviewerApiViewSet.as_view({'get': 'list'}) - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) self.response = self.view(self.request) def test_can_access(self): diff --git a/core/tests/test_submissiontypeview.py b/core/tests/test_submissiontypeview.py index c1c7f4b0..a369a726 100644 --- a/core/tests/test_submissiontypeview.py +++ b/core/tests/test_submissiontypeview.py @@ -23,7 +23,7 @@ class SubmissionTypeViewTest(APITestCase): full_score=20, description='Whatever') force_authenticate(self.request, - self.user_factory.make_reviewer().user) + self.user_factory.make_reviewer()) self.view = SubmissionTypeApiView.as_view({'get': 'list'}) self.response = self.view(self.request) diff --git a/core/tests/test_subscription_assignment_service.py b/core/tests/test_subscription_assignment_service.py index d96aae0e..20f81577 100644 --- a/core/tests/test_subscription_assignment_service.py +++ b/core/tests/test_subscription_assignment_service.py @@ -20,12 +20,14 @@ class GeneralTaskSubscriptionRandomTest(TestCase): self.submission_type = SubmissionType.objects.create( name='submission_01', full_score=14) self.submission_01 = Submission.objects.create( - type=self.submission_type, student=self.s1, text='I really failed') + type=self.submission_type, student=self.s1.student, + text='I really failed') self.submission_02 = Submission.objects.create( - type=self.submission_type, student=self.s2, text='I like apples') + type=self.submission_type, student=self.s2.student, + text='I like apples') self.subscription = GeneralTaskSubscription.objects.create( - owner=self.t.user, query_type=GeneralTaskSubscription.RANDOM) + owner=self.t, query_type=GeneralTaskSubscription.RANDOM) def test_subscription_gets_an_assignment(self): self.subscription._create_new_assignment_if_subscription_empty() diff --git a/core/tests/test_tutor_api_endpoints.py b/core/tests/test_tutor_api_endpoints.py index 8c8f59a5..908626d1 100644 --- a/core/tests/test_tutor_api_endpoints.py +++ b/core/tests/test_tutor_api_endpoints.py @@ -10,7 +10,7 @@ from rest_framework.reverse import reverse from rest_framework.test import (APIClient, APIRequestFactory, APITestCase, force_authenticate) -from core.models import Feedback, Tutor +from core.models import Feedback from core.views import TutorApiViewSet from util.factories import GradyUserFactory @@ -31,16 +31,16 @@ class TutorDeleteTest(APITestCase): args=['UFO'])) self.view = TutorApiViewSet.as_view({'delete': 'destroy'}) - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) self.response = self.view(self.request, username='UFO') def test_can_delete_tutor_soapbox(self): """ see if the tutor was deleted """ - self.assertEqual(0, Tutor.objects.count()) + self.assertEqual(0, get_user_model().get_tutors().count()) def test_user_is_deleted_too(self): """ see if the associated user was deleted (reviewer remains) """ - self.assertEqual(1, get_user_model().objects.count()) + self.assertNotIn(self.tutor, get_user_model().objects.all()) class TutorListTests(APITestCase): @@ -57,7 +57,7 @@ class TutorListTests(APITestCase): self.request = self.factory.get(reverse('tutor-list')) self.view = TutorApiViewSet.as_view({'get': 'list'}) - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) self.response = self.view(self.request) def test_can_access(self): @@ -68,7 +68,7 @@ class TutorListTests(APITestCase): def test_feedback_count_matches_database(self): def verify_fields(tutor_obj): - t = Tutor.objects.get(user__username=tutor_obj['username']) + t = get_user_model().objects.get(username=tutor_obj['username']) return t.get_feedback_count() == tutor_obj['feedback_count'] self.assertTrue(all(map(verify_fields, self.response.data))) @@ -81,7 +81,7 @@ class TutorListTests(APITestCase): class TutorCreateTests(APITestCase): - USERNAME = 'some weird name!' + USERNAME = 'some_weird_name' @classmethod def setUpTestData(cls): @@ -94,14 +94,15 @@ class TutorCreateTests(APITestCase): {'username': self.USERNAME}) self.view = TutorApiViewSet.as_view({'post': 'create'}) - force_authenticate(self.request, user=self.reviewer.user) + force_authenticate(self.request, user=self.reviewer) self.response = self.view(self.request, username=self.USERNAME) def test_can_access(self): self.assertEqual(self.response.status_code, status.HTTP_201_CREATED) def test_can_create(self): - self.assertEqual(Tutor.objects.first().user.username, self.USERNAME) + self.assertEqual(self.USERNAME, + get_user_model().get_tutors().first().username) class TutorDetailViewTests(APITestCase): @@ -115,7 +116,7 @@ class TutorDetailViewTests(APITestCase): self.tutor = self.user_factory.make_tutor(username='fetterotto') self.reviewer = self.user_factory.make_reviewer() self.client = APIClient() - self.client.force_authenticate(user=self.reviewer.user) + self.client.force_authenticate(user=self.reviewer) url = reverse('tutor-detail', kwargs={'username': 'fetterotto'}) self.response = self.client.get(url, format='json') @@ -125,4 +126,4 @@ class TutorDetailViewTests(APITestCase): def test_can_view_tutor(self): self.assertEqual(self.response.data['username'], - self.tutor.user.username) + self.tutor.username) diff --git a/core/urls.py b/core/urls.py index 7898cedd..3fc44639 100644 --- a/core/urls.py +++ b/core/urls.py @@ -6,11 +6,12 @@ from core import views # Create a router and register our viewsets with it. router = DefaultRouter() -router.register('student', views.StudentReviewerApiViewSet) +router.register('student', views.StudentReviewerApiViewSet, + base_name='student') router.register('examtype', views.ExamApiViewSet) router.register('feedback', views.FeedbackApiView) router.register('submissiontype', views.SubmissionTypeApiView) -router.register('tutor', views.TutorApiViewSet) +router.register('tutor', views.TutorApiViewSet, base_name='tutor') router.register('subscription', views.SubscriptionApiViewSet) router.register('assignment', views.AssignmentApiViewSet) diff --git a/core/views.py b/core/views.py index 140ef2e0..fc584ded 100644 --- a/core/views.py +++ b/core/views.py @@ -7,14 +7,16 @@ from rest_framework.decorators import api_view, detail_route from rest_framework.response import Response from core import models -from core.models import (ExamType, Feedback, GeneralTaskSubscription, Student, - SubmissionType, Tutor, TutorSubmissionAssignment) +from core.models import (ExamType, GeneralTaskSubscription, StudentInfo, + SubmissionType, TutorSubmissionAssignment, + Feedback) from core.permissions import IsReviewer, IsStudent, IsTutorOrReviewer -from core.serializers import (AssignmentDetailSerializer, AssignmentSerializer, - ExamSerializer, FeedbackSerializer, - StudentSerializer, StudentSerializerForListView, +from core.serializers import (AssignmentSerializer, ExamSerializer, + StudentInfoSerializer, + StudentInfoSerializerForListView, SubmissionTypeSerializer, SubscriptionSerializer, - TutorSerializer) + TutorSerializer, FeedbackSerializer, + AssignmentDetailSerializer) @api_view() @@ -24,16 +26,15 @@ def get_jwt_expiration_delta(request): @api_view() def get_user_role(request): - return Response({'role': - type(request.user.get_associated_user()).__name__}) + return Response({'role': request.user.role}) class StudentSelfApiView(generics.RetrieveAPIView): """ Gets all data that belongs to one student """ permission_classes = (IsStudent,) - serializer_class = StudentSerializer + serializer_class = StudentInfoSerializer - def get_object(self) -> Student: + def get_object(self) -> StudentInfo: """ 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 @@ -65,21 +66,17 @@ class TutorApiViewSet( viewsets.GenericViewSet): """ Api endpoint for creating, listing, viewing or deleteing tutors """ permission_classes = (IsReviewer,) - queryset = Tutor.objects.all() + queryset = models.UserAccount.objects.filter(role='Tutor') serializer_class = TutorSerializer - lookup_field = 'user__username' + lookup_field = 'username' lookup_url_kwarg = 'username' - def perform_destroy(self, instance): - """ deletes the tutors account and model (on delete cascade) """ - instance.user.delete() - class StudentReviewerApiViewSet(viewsets.ReadOnlyModelViewSet): """ Gets a list of all students without individual submissions """ permission_classes = (IsReviewer,) - queryset = Student.objects.all() - serializer_class = StudentSerializerForListView + queryset = StudentInfo.objects.all() + serializer_class = StudentInfoSerializerForListView class SubmissionTypeApiView(viewsets.ReadOnlyModelViewSet): diff --git a/delbert.py b/delbert.py index d3526487..0d29750f 100755 --- a/delbert.py +++ b/delbert.py @@ -11,7 +11,7 @@ django.setup() from django.contrib.auth.models import User import util.importer -from core.models import Student, Submission +from core.models import StudentInfo, Submission unused_variable = [] @@ -92,7 +92,7 @@ def handle_passwordlist(output=sys.stdout, instance="", **kwargs): writer = csv.writer(output) writer.writerow(['Name', 'Matrikel', 'Username', 'password', 'instance']) - for student in Student.objects.all(): + for student in StudentInfo.objects.all(): password = ''.join(secrets.choice(choose_from) for _ in range(3)) student.user.set_password(password) @@ -116,7 +116,7 @@ def handle_enableusers(switch, exclude, include, **kwargs): def handle_replaceusernames(matno2username_dict, **kwargs): matno2username = json.JSONDecoder().decode(matno2username_dict.read()) - for student in Student.objects.all(): + for student in StudentInfo.objects.all(): if student.matrikel_no in matno2username: new_name = matno2username[student.matrikel_no] student.user.username = new_name diff --git a/util/factories.py b/util/factories.py index a27f20bd..463fb549 100644 --- a/util/factories.py +++ b/util/factories.py @@ -3,8 +3,8 @@ import secrets import string from core.models import UserAccount as User -from core.models import (ExamType, Feedback, Reviewer, Student, Submission, - SubmissionType, Tutor) +from core.models import (ExamType, Feedback, StudentInfo, Submission, + SubmissionType) STUDENTS = 'students' TUTORS = 'tutors' @@ -44,22 +44,35 @@ class GradyUserFactory: def _get_random_name(self, prefix='', suffix='', k=4): return ''.join((prefix, self.make_password(k), suffix)) - def _make_base_user(self, username, groupname, password=None, + def _get_group_for_user_role(self, role): + """ Returns the groupname for a role """ + return { + 'Student': 'students', + 'Tutor': 'tutors', + 'Reviewer': 'reviewers' + }[role] + + def _make_base_user(self, username, role, password=None, store_pw=False, **kwargs): """ This is a specific wrapper for the django update_or_create method of objects. - * A new user is created and password and group are set accordingly - * If the user was there before password is NOT change but group is. - * A user must only have one group. + * If now username is passed, a generic one will be generated + * A new user is created and password and role are set accordingly + * If the user was there before password IS changed + * A user must only have one role. Returns: - (User object, str): The user object that was added to the group and + (User object, str): The user object that was added to the role and the password of that user if it was created. """ + if not username: + username = self._get_random_name(prefix=role.lower() + '_') + username = username.strip() user, created = User.objects.update_or_create( username=username, + role=role, defaults=kwargs) if created: @@ -68,51 +81,33 @@ class GradyUserFactory: user.save() if created and store_pw: - self.password_storge(username, groupname, password) + self.password_storge( + username, + self._get_group_for_user_role(role), + password) return user - def _get_user_model_for_group(self, groupname): - """ Returns the model class for a usergroup """ - return { - STUDENTS: Student, - TUTORS: Tutor, - REVIEWERS: Reviewer, - }[groupname] - - def _make_user_generic(self, username, groupname, **kwargs): - """ Provides a model with a associated user but without any defaults - """ - - if not username: - username = self._get_random_name(prefix=groupname.lower() + '_') - - model = self._get_user_model_for_group(groupname) - user = self._make_base_user(username, groupname, **kwargs) - - generic_user, _ = model.objects.get_or_create(user=user) - - return generic_user - def make_student(self, username=None, matrikel_no=None, exam=None, **kwargs): """ Creates a student. Defaults can be passed via kwargs like in relation managers objects.update method. """ - user = self._make_user_generic(username, STUDENTS, **kwargs) + user = self._make_base_user(username, 'Student', **kwargs) + studentInfo = StudentInfo.objects.get_or_create(user=user)[0] if matrikel_no: - user.matrikel_no = matrikel_no + studentInfo.matrikel_no = matrikel_no if exam: - user.exam = exam - user.save() + studentInfo.exam = exam + studentInfo.save() return user def make_tutor(self, username=None, **kwargs): """ Creates or updates a tutor if needed with defaults """ - return self._make_user_generic(username, TUTORS, **kwargs) + return self._make_base_user(username, 'Tutor', **kwargs) def make_reviewer(self, username=None, **kwargs): """ Creates or updates a reviewer if needed with defaults """ - return self._make_user_generic(username, REVIEWERS, **kwargs) + return self._make_base_user(username, 'Reviewer', **kwargs) def make_exams(exams=[], **kwargs): @@ -146,7 +141,7 @@ def make_reviewers(reviewers=[], **kwargs): def make_feedback(feedback, submission_object): feedback['of_tutor'] = User.objects.get( - username=feedback['of_tutor']).get_associated_user().user + username=feedback['of_tutor']) return Feedback.objects.update_or_create( of_submission=submission_object, defaults=feedback)[0] @@ -157,7 +152,7 @@ def make_submissions(submissions=[], **kwargs): for submission in submissions: submission_type, _ = SubmissionType.objects.get_or_create( name=submission.get('type', 'Auto generated type')) - student, _ = Student.objects.get_or_create(user=User.objects.get( + student, _ = StudentInfo.objects.get_or_create(user=User.objects.get( username=submission.get('user', 'default_user') )) submission_object, _ = Submission.objects.get_or_create( diff --git a/util/importer.py b/util/importer.py index d6f30804..c445545c 100644 --- a/util/importer.py +++ b/util/importer.py @@ -7,7 +7,7 @@ from typing import Callable import util.convert import util.processing from core.models import UserAccount as User -from core.models import (ExamType, Feedback, Student, Submission, +from core.models import (ExamType, Feedback, StudentInfo, Submission, SubmissionType, Test) from util.factories import REVIEWERS, STUDENTS, TUTORS, GradyUserFactory from util.messages import info, warn @@ -84,7 +84,7 @@ def add_user(username, group, **kwargs): def add_student(username, email, submissions, **kwargs): user = add_user(username, STUDENTS, email=email) - student, _ = Student.objects.update_or_create( + student, _ = StudentInfo.objects.update_or_create( user=user, defaults={'user': user, **kwargs} ) -- GitLab