Skip to content

Commit fbdbbc9

Browse files
committed
Added participation tags for ParticipationPreapproval.
1 parent d581bc1 commit fbdbbc9

6 files changed

+100
-86
lines changed

course/enrollment.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,17 @@ def email_suffix_matches(email, suffix):
259259
roles = ParticipationRole.objects.filter(
260260
course=course,
261261
is_default_for_new_participants=True)
262+
tags = None
262263

263264
if preapproval is not None:
264265
roles = list(preapproval.roles.all())
266+
tags = list(preapproval.tags.all())
265267

266268
try:
267269
if course.enrollment_approval_required and preapproval is None:
268270
participation = handle_enrollment_request(
269271
course, user, participation_status.requested,
270-
roles, request)
272+
roles, tags, request)
271273

272274
assert participation is not None
273275

@@ -305,7 +307,7 @@ def email_suffix_matches(email, suffix):
305307
"by email once your request has been acted upon."))
306308
else:
307309
handle_enrollment_request(course, user, participation_status.active,
308-
roles, request)
310+
roles, tags, request)
309311

310312
messages.add_message(request, messages.SUCCESS,
311313
_("Successfully enrolled."))
@@ -318,8 +320,8 @@ def email_suffix_matches(email, suffix):
318320

319321

320322
@transaction.atomic
321-
def handle_enrollment_request(course, user, status, roles, request=None):
322-
# type: (Course, Any, Text, Optional[List[ParticipationRole]], Optional[http.HttpRequest]) -> Participation # noqa
323+
def handle_enrollment_request(course, user, status, roles, tags, request=None):
324+
# type: (Course, Any, Text, Optional[List[ParticipationRole]], Optional[List[ParticipationTag]], Optional[http.HttpRequest]) -> Participation # noqa
323325
participations = Participation.objects.filter(course=course, user=user)
324326

325327
assert participations.count() <= 1
@@ -338,6 +340,9 @@ def handle_enrollment_request(course, user, status, roles, request=None):
338340
if roles is not None:
339341
participation.roles.set(roles)
340342

343+
if tags is not None:
344+
participation.tags.set(tags)
345+
341346
if status == participation_status.active:
342347
send_enrollment_decision(participation, True, request)
343348
elif status == participation_status.denied:
@@ -447,6 +452,12 @@ def __init__(self, course, *args, **kwargs):
447452
.filter(course=course)
448453
),
449454
label=_("Roles"))
455+
self.fields["tags"] = forms.ModelMultipleChoiceField(
456+
queryset=(
457+
ParticipationTag.objects
458+
.filter(course=course)
459+
),
460+
label=_("Tags"), required=False)
450461
self.fields["preapproval_type"] = forms.ChoiceField(
451462
choices=(
452463
("email", _("Email")),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 3.0.8 on 2020-10-28 07:23
2+
3+
import course.models
4+
import django.core.validators
5+
from django.db import migrations, models
6+
import jsonfield.fields
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('course', '0113_merge_20190919_1408'),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='participationpreapproval',
18+
name='tags',
19+
field=models.ManyToManyField(blank=True, to='course.ParticipationTag', verbose_name='Participation tags'),
20+
),
21+
]

course/models.py

+2
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ class ParticipationPreapproval(models.Model):
618618
verbose_name=_("Role (unused)"),)
619619
roles = models.ManyToManyField(ParticipationRole, blank=True,
620620
verbose_name=_("Roles"), related_name="+")
621+
tags = models.ManyToManyField(ParticipationTag, blank=True,
622+
verbose_name=_("Participation tags"))
621623

622624
creator = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
623625
verbose_name=_("Creator"), on_delete=models.SET_NULL)

course/receivers.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
ParticipationPreapproval,
3535
)
3636

37-
from typing import List, Union, Text, Optional, Tuple, Any # noqa
37+
from typing import List, Union, Text, Optional, Tuple, Any, TYPE_CHECKING # noqa
38+
if TYPE_CHECKING:
39+
from course.models import ParticipationTag, ParticipationRole # noqa
3840

3941

4042
# {{{ Update enrollment status when a User/Course instance is saved
@@ -51,6 +53,8 @@ def update_requested_participation_status(sender, created, instance,
5153

5254
user_updated = False
5355
course_updated = False
56+
course = None
57+
user = None
5458

5559
if isinstance(instance, Course):
5660
course_updated = True
@@ -71,20 +75,22 @@ def update_requested_participation_status(sender, created, instance,
7175
assert user_updated
7276
course = requested.course
7377

74-
may_preapprove, roles = may_preapprove_role(course, user)
78+
assert course is not None
79+
assert user is not None
80+
may_preapprove, roles, tags = may_preapprove_role_and_tag(course, user)
7581

7682
if may_preapprove:
7783
from course.enrollment import handle_enrollment_request
7884

7985
handle_enrollment_request(
80-
course, user, participation_status.active, roles)
86+
course, user, participation_status.active, roles, tags)
8187

8288

83-
def may_preapprove_role(course, user):
84-
# type: (Course, User) -> Tuple[bool, Optional[List[Text]]]
89+
def may_preapprove_role_and_tag(course, user):
90+
# type: (Course, User) -> Tuple[bool, Optional[List[ParticipationRole]], Optional[List[ParticipationTag]]] # noqa
8591

8692
if not user.is_active:
87-
return False, None
93+
return False, None, None
8894

8995
preapproval = None
9096
if user.email:
@@ -105,9 +111,9 @@ def may_preapprove_role(course, user):
105111
pass
106112

107113
if preapproval:
108-
return True, list(preapproval.roles.all())
114+
return True, list(preapproval.roles.all()), list(preapproval.tags.all())
109115
else:
110-
return False, None
116+
return False, None, None
111117

112118
# }}}
113119

tests/factories.py

+15
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,21 @@ def roles(self, create, extracted, **kwargs):
263263
role = ParticipationRoleFactory(course=self.course)
264264
self.roles.set([role])
265265

266+
@factory.post_generation
267+
def tags(self, create, extracted, **kwargs):
268+
if not create:
269+
# Simple build, do nothing.
270+
return
271+
if extracted:
272+
for tag in extracted:
273+
if isinstance(tag, str):
274+
tag = ParticipationTagFactory(
275+
course=self.course, name=tag)
276+
else:
277+
assert isinstance(tag, models.ParticipationTag)
278+
self.tags.set([tag])
279+
return
280+
266281

267282
def generate_random_hash():
268283
import hashlib

tests/test_enrollment.py

+33-74
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,34 @@ def test_preapprved_user_updated_inst_id_after_req_enrollment_roles_match(self):
422422
self.assertIn(expected_role_identifier,
423423
[role.identifier for role in user_participation.roles.all()])
424424

425+
def test_preapprved_user_updated_inst_id_after_req_enrollment_tags_match(self):
426+
self.update_require_approval_course(
427+
preapproval_require_verified_inst_id=True)
428+
429+
user = factories.UserFactory()
430+
inst_id = user.institutional_id
431+
432+
# Temporarily remove his/her inst_id
433+
user.institutional_id = None
434+
user.save()
435+
436+
expected_tag_name = "test_student_tag"
437+
438+
self.get_test_preapproval(
439+
institutional_id=inst_id, tags=[expected_tag_name])
440+
441+
with self.temporarily_switch_to_user(user):
442+
self.c.post(self.enroll_request_url)
443+
444+
# Add back the inst_id
445+
user.institutional_id = inst_id
446+
user.institutional_id_verified = True
447+
user.save()
448+
449+
user_participation = Participation.objects.get(user=user)
450+
self.assertIn(expected_tag_name,
451+
[tag.name for tag in user_participation.tags.all()])
452+
425453
def test_course_require_inst_id_verified_user_inst_id_not_verified1(self):
426454
# thought matched
427455
self.update_require_approval_course(
@@ -553,7 +581,7 @@ def test_approve_new(self):
553581
request = mock.MagicMock()
554582

555583
participation = enrollment.handle_enrollment_request(
556-
self.course, user, status, roles, request=request)
584+
self.course, user, status, roles, None, request=request)
557585

558586
self.assertEqual(participation.user, user)
559587
self.assertEqual(participation.status, status)
@@ -570,7 +598,7 @@ def test_approve_new_none_roles(self):
570598
request = mock.MagicMock()
571599

572600
participation = enrollment.handle_enrollment_request(
573-
self.course, user, status, roles, request=request)
601+
self.course, user, status, roles, None, request=request)
574602

575603
self.assertEqual(participation.user, user)
576604
self.assertEqual(participation.status, status)
@@ -589,7 +617,7 @@ def test_deny_new(self):
589617
request = mock.MagicMock()
590618

591619
participation = enrollment.handle_enrollment_request(
592-
self.course, user, status, roles, request=request)
620+
self.course, user, status, roles, None, request=request)
593621

594622
self.assertEqual(participation.user, user)
595623
self.assertEqual(participation.status, status)
@@ -611,7 +639,7 @@ def test_approve_requested(self):
611639
request = mock.MagicMock()
612640

613641
participation = enrollment.handle_enrollment_request(
614-
self.course, user, status, roles, request=request)
642+
self.course, user, status, roles, None, request=request)
615643

616644
self.assertEqual(participation.user, user)
617645
self.assertEqual(participation.status, status)
@@ -634,7 +662,7 @@ def test_deny_requested(self):
634662
request = mock.MagicMock()
635663

636664
participation = enrollment.handle_enrollment_request(
637-
self.course, user, status, roles, request=request)
665+
self.course, user, status, roles, None, request=request)
638666

639667
self.assertEqual(participation.user, user)
640668
self.assertEqual(participation.status, status)
@@ -1002,75 +1030,6 @@ def test_edit_participation_view_save_integrity_error(self):
10021030
self.assertEqual(len(mail.outbox), 0)
10031031

10041032

1005-
class EnrollmentPreapprovalTestMixin(LocmemBackendTestsMixin,
1006-
EnrollmentTestBaseMixin):
1007-
1008-
@classmethod
1009-
def setUpTestData(cls): # noqa
1010-
super(EnrollmentPreapprovalTestMixin, cls).setUpTestData()
1011-
cls.non_ptcp_active_user1.institutional_id_verified = True
1012-
cls.non_ptcp_active_user1.save()
1013-
cls.non_ptcp_active_user2.institutional_id_verified = False
1014-
cls.non_ptcp_active_user2.save()
1015-
1016-
@property
1017-
def preapprove_data_emails(self):
1018-
preapproved_user = [self.non_ptcp_active_user1,
1019-
self.non_ptcp_active_user2]
1020-
preapproved_data = [u.email for u in preapproved_user]
1021-
preapproved_data.insert(1, " ") # empty line
1022-
preapproved_data.insert(0, " ") # empty line
1023-
return preapproved_data
1024-
1025-
@property
1026-
def preapprove_data_institutional_ids(self):
1027-
preapproved_user = [self.non_ptcp_active_user1,
1028-
self.non_ptcp_active_user2,
1029-
self.non_ptcp_unconfirmed_user1]
1030-
preapproved_data = [u.institutional_id for u in preapproved_user]
1031-
preapproved_data.insert(1, " ") # empty line
1032-
preapproved_data.insert(0, " ") # empty line
1033-
return preapproved_data
1034-
1035-
@property
1036-
def preapproval_url(self):
1037-
return reverse("relate-create_preapprovals",
1038-
args=[self.course.identifier])
1039-
1040-
@property
1041-
def default_preapprove_role(self):
1042-
role, _ = (ParticipationRole.objects.get_or_create(
1043-
course=self.course, identifier="student"))
1044-
return [str(role.pk)]
1045-
1046-
def post_preapproval(self, preapproval_type, preapproval_data=None,
1047-
force_login_instructor=True):
1048-
if preapproval_data is None:
1049-
if preapproval_type == "email":
1050-
preapproval_data = self.preapprove_data_emails
1051-
elif preapproval_type == "institutional_id":
1052-
preapproval_data = self.preapprove_data_institutional_ids
1053-
1054-
assert preapproval_data is not None
1055-
assert isinstance(preapproval_data, list)
1056-
1057-
data = {
1058-
"preapproval_type": [preapproval_type],
1059-
"preapproval_data": ["\n".join(preapproval_data)],
1060-
"roles": self.student_role_post_data,
1061-
"submit": [""]
1062-
}
1063-
if not force_login_instructor:
1064-
approver = self.get_logged_in_user()
1065-
else:
1066-
approver = self.instructor_participation.user
1067-
with self.temporarily_switch_to_user(approver):
1068-
return self.c.post(self.preapproval_url, data, follow=True)
1069-
1070-
def get_preapproval_count(self):
1071-
return ParticipationPreapproval.objects.all().count()
1072-
1073-
10741033
class CreatePreapprovalsTest(EnrollmentTestMixin,
10751034
SingleCourseTestMixin, TestCase):
10761035
# test enrollment.create_preapprovals

0 commit comments

Comments
 (0)