Skip to content

Commit

Permalink
Add PENDING enrollment status for SIS enrollments
Browse files Browse the repository at this point in the history
The PENDING enrollment status is used for courses with enrollment
questionnaires, when students are enrolled from SIS. PENDING students
cannot start the course tasks before completing the enrollment
questionnaire.

Use the PENDING status also for non-SIS courses when the student
opens the enrollment questionnaire. If they do not submit
the questionnaire successfully, the PENDING enrollment will stay
in A+.

PENDING enrollment may be activated manually to the ACTIVE status
(in the `enroll_student` function).

Fixes #981.
  • Loading branch information
PasiSa authored and markkuriekkinen committed Mar 31, 2022
1 parent c5133c9 commit 389e2b2
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 15 deletions.
18 changes: 18 additions & 0 deletions course/migrations/0055_alter_enrollment_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-03-29 07:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('course', '0054_courseinstance_sis_enroll'),
]

operations = [
migrations.AlterField(
model_name='enrollment',
name='status',
field=models.IntegerField(choices=[(1, 'ACTIVE'), (2, 'REMOVED'), (3, 'BANNED'), (4, 'PENDING')], default=1, verbose_name='LABEL_STATUS'),
),
]
31 changes: 25 additions & 6 deletions course/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class Enrollment(models.Model):
('ACTIVE', 1, _('ACTIVE')),
('REMOVED', 2, _('REMOVED')),
('BANNED', 3, _('BANNED')),
('PENDING', 4, _('PENDING')),
])

course_instance = models.ForeignKey('CourseInstance',
Expand Down Expand Up @@ -461,9 +462,11 @@ def get_visible(self, user=None):
return self.all()

def get_enrolled(self, user):
# Also "PENDING" enrollments should show as own courses (e.g. in front page, course menu)
return self.filter(
Q(enrollment__status=Enrollment.ENROLLMENT_STATUS.ACTIVE)
| Q(enrollment__status=Enrollment.ENROLLMENT_STATUS.PENDING),
enrollment__role=Enrollment.ENROLLMENT_ROLE.STUDENT,
enrollment__status=Enrollment.ENROLLMENT_STATUS.ACTIVE,
enrollment__user_profile=user)

def get_assisting(self, user):
Expand Down Expand Up @@ -839,8 +842,12 @@ def is_enrollable(self, user):
return True
return False

def enroll_student(self, user, from_sis=False):
def enroll_student(self, user, from_sis=False, use_pending=False):
# Return value False indicates whether that the user was already enrolled.
if use_pending:
status = Enrollment.ENROLLMENT_STATUS.PENDING
else:
status = Enrollment.ENROLLMENT_STATUS.ACTIVE
if user and user.is_authenticated:
try:
enrollment = Enrollment.objects.get(
Expand All @@ -849,14 +856,23 @@ def enroll_student(self, user, from_sis=False):
)
if (
enrollment.role == Enrollment.ENROLLMENT_ROLE.STUDENT
and enrollment.status == Enrollment.ENROLLMENT_STATUS.ACTIVE
and enrollment.status in (
Enrollment.ENROLLMENT_STATUS.ACTIVE,
Enrollment.ENROLLMENT_STATUS.PENDING,
)
):
if (enrollment.status == Enrollment.ENROLLMENT_STATUS.PENDING and not use_pending):
enrollment.status = Enrollment.ENROLLMENT_STATUS.ACTIVE
enrollment.save()

if not enrollment.from_sis and from_sis:
enrollment.from_sis = from_sis
enrollment.save()

return False

enrollment.role = Enrollment.ENROLLMENT_ROLE.STUDENT
enrollment.status = Enrollment.ENROLLMENT_STATUS.ACTIVE
enrollment.status = status
enrollment.from_sis = from_sis
enrollment.save()
return True
Expand All @@ -865,7 +881,7 @@ def enroll_student(self, user, from_sis=False):
course_instance=self,
user_profile=user.userprofile,
role=Enrollment.ENROLLMENT_ROLE.STUDENT,
status=Enrollment.ENROLLMENT_STATUS.ACTIVE,
status=status,
from_sis=from_sis,
)
return True
Expand Down Expand Up @@ -894,10 +910,13 @@ def enroll_from_sis(self) -> int:
logger.exception(f"Error in getting participants from SIS.")
return -1

from exercise.models import LearningObject
use_pending = bool(LearningObject.objects.find_enrollment_exercise(self, False))

for i in participants:
try:
profile = UserProfile.get_by_student_id(i)
if self.enroll_student(profile.user, from_sis=True):
if self.enroll_student(profile.user, from_sis=True, use_pending=use_pending):
count = count + 1

except UserProfile.DoesNotExist:
Expand Down
16 changes: 14 additions & 2 deletions course/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def get_resource_objects(self):
def get(self, request, *args, **kwargs):
return self.redirect(self.course_instance.url)

class InstanceView(EnrollableViewMixin, BaseTemplateView):
class InstanceView(EnrollableViewMixin, BaseRedirectMixin, BaseTemplateView):
access_mode = ACCESS.STUDENT
# ACCESS.STUDENT requires users to log in, but the access mode is dropped
# in public courses. CourseVisiblePermission has more restrictions as well.
Expand All @@ -154,6 +154,17 @@ def get(self, request, *args, **kwargs):

later_instance = None

# PENDING student has enrolled, but not yet responded to the enrollment questionnaire.
if request.user.is_authenticated:
enrollment = self.user_course_data
if enrollment and enrollment.status == Enrollment.ENROLLMENT_STATUS.PENDING:
exercise = LearningObject.objects.find_enrollment_exercise(
self.instance, self.profile.is_external)
# For PENDING student, it should not be possible for exercise to be null,
# but better be careful. In that case, proceeding to the course seems OK.
if exercise:
return self.redirect(exercise.get_absolute_url())

if self.instance.is_past:
try:
later_instance = (
Expand Down Expand Up @@ -186,8 +197,9 @@ def post(self, request, *args, **kwargs):

# Support enrollment questionnaires.
exercise = LearningObject.objects.find_enrollment_exercise(
self.instance, self.profile)
self.instance, self.profile.is_external)
if exercise:
self.instance.enroll_student(self.request.user, from_sis=False, use_pending=True)
return self.redirect(exercise.get_absolute_url())

self.instance.enroll_student(self.request.user)
Expand Down
4 changes: 2 additions & 2 deletions exercise/exercise_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ def get_queryset(self):
.prefetch_related('parent')
)

def find_enrollment_exercise(self, course_instance, profile):
def find_enrollment_exercise(self, course_instance, is_external):
exercise = None
if profile.is_external:
if is_external:
exercise = self.filter(
course_module__course_instance=course_instance,
status='enrollment_ext'
Expand Down
2 changes: 1 addition & 1 deletion exercise/staff_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ def get_common_objects(self):

exercise = LearningObject.objects.find_enrollment_exercise(
self.instance,
profile
profile.is_external,
)
if exercise:
submissions = exercise.get_submissions_for_student(profile)
Expand Down
8 changes: 6 additions & 2 deletions locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ msgstr "Removed"
msgid "BANNED"
msgstr "Banned"

#: course/models.py
msgid "PENDING"
msgstr "Pending"

#: course/models.py
msgid "LABEL_USER_PROFILE"
msgstr "user"
Expand Down Expand Up @@ -1334,11 +1338,11 @@ msgstr "Filter users by status"

#: course/templates/course/staff/participants.html
msgid "NUMBER_OF_ENROLLED_STUDENTS"
msgstr "Number of enrolled students"
msgstr "Number of active students"

#: course/templates/course/staff/participants.html
msgid "REMOVED_STUDENTS"
msgstr "removed students"
msgstr "inactive students"

#: course/templates/course/staff/participants.html
msgid "OF_WHICH_SELECTED"
Expand Down
8 changes: 6 additions & 2 deletions locale/fi/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ msgstr "Poistettu"
msgid "BANNED"
msgstr "Estetty"

#: course/models.py
msgid "PENDING"
msgstr "Odottava"

#: course/models.py
msgid "LABEL_USER_PROFILE"
msgstr "käyttäjä"
Expand Down Expand Up @@ -1341,11 +1345,11 @@ msgstr "Suodata listaa tilan perusteella"

#: course/templates/course/staff/participants.html
msgid "NUMBER_OF_ENROLLED_STUDENTS"
msgstr "Ilmoittautuneiden opiskelijoiden määrä"
msgstr "Aktiivisten opiskelijoiden määrä"

#: course/templates/course/staff/participants.html
msgid "REMOVED_STUDENTS"
msgstr "poistettuja opiskelijoita"
msgstr "ei-aktiivisia opiskelijoita"

#: course/templates/course/staff/participants.html
msgid "OF_WHICH_SELECTED"
Expand Down

0 comments on commit 389e2b2

Please sign in to comment.