import csv
import tempfile
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils import timezone
from intranet.utils.date import get_senior_graduation_year
from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor
from ..views.attendance import generate_roster_pdf
from .eighth_test import EighthAbstractTest
[docs]class EighthAttendanceTestCase(EighthAbstractTest):
"""Test cases for ``views.attendance``."""
[docs] def test_take_attendance(self):
"""Makes sure that taking attendance for activities with multiple students signed up works."""
self.make_admin()
user1 = get_user_model().objects.create(
username="user1", graduation_year=get_senior_graduation_year() + 1, student_id=12345, first_name="Test", last_name="User"
)
user2 = get_user_model().objects.create(
username="user2", graduation_year=get_senior_graduation_year() + 1, student_id=12346, first_name="TestTwo", last_name="UserTwo"
)
user3 = get_user_model().objects.create(
username="user3", graduation_year=get_senior_graduation_year() + 1, student_id=12347, first_name="TestThree", last_name="UserThree"
)
block1 = self.add_block(date="3000-11-11", block_letter="A")
room1 = self.add_room(name="room1", capacity=5)
act1 = self.add_activity(name="Test Activity 1")
act1.rooms.add(room1)
schact1 = self.schedule_activity(act1.id, block1.id, capacity=5)
schact1.attendance_taken = False
schact1.add_user(user1)
schact1.add_user(user2)
schact1.add_user(user3)
schact1.save()
# Simulate taking attendance with user1 and user3 present, but user2 absent.
response = self.client.post(reverse("eighth_take_attendance", args=[schact1.id]), data={user1.id: "on", user3.id: "on"})
self.assertEqual(response.status_code, 302)
# Make sure activity is marked as attendance taken.
self.assertTrue(EighthScheduledActivity.objects.get(id=schact1.id).attendance_taken)
# Make sure EighthSignup object hasn't been marked absent for user1.
self.assertFalse(EighthSignup.objects.get(user=user1, scheduled_activity=schact1).was_absent)
# Make sure EighthSignup object was marked absent for user2.
self.assertTrue(EighthSignup.objects.get(user=user2, scheduled_activity=schact1).was_absent)
# Make sure EighthSignup object hasn't been marked absent for user3.
self.assertFalse(EighthSignup.objects.get(user=user3, scheduled_activity=schact1).was_absent)
[docs] def test_take_attendance_clear_bit(self):
"""Tests the "clear attendance bit" feature."""
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
# Take attendance
scheduled.attendance_taken = True
scheduled.save()
# Log in
self.make_admin()
# Clear the bit
response = self.client.post(
reverse("eighth_take_attendance", kwargs={"scheduled_activity_id": scheduled.id}), data={"clear_attendance_bit": True}, follow=True
)
self.assertEqual(200, response.status_code)
self.assertFalse(EighthScheduledActivity.objects.get(block=block, activity=activity).attendance_taken)
[docs] def test_take_attendance_google_meet_csv(self):
"""Make sure taking attendance through an uploaded Google Meet file works."""
self.make_admin()
user1 = get_user_model().objects.create(
username="user1", graduation_year=get_senior_graduation_year() + 1, student_id=12345, first_name="Test", last_name="User"
)
user2 = get_user_model().objects.create(
username="user2", graduation_year=get_senior_graduation_year() + 1, student_id=12346, first_name="TestTwo", last_name="UserTwo"
)
block1 = self.add_block(date="3000-11-11", block_letter="A")
room1 = self.add_room(name="room1", capacity=5)
act1 = self.add_activity(name="Test Activity 1")
act1.rooms.add(room1)
schact1 = self.schedule_activity(act1.id, block1.id, capacity=5)
schact1.attendance_taken = False
schact1.add_user(user1)
schact1.add_user(user2)
schact1.save()
with tempfile.NamedTemporaryFile(mode="w+") as f:
writer = csv.DictWriter(f, fieldnames=["Name", "Email"])
writer.writeheader()
writer.writerow({"Name": "Test User", "Email": "12345@fcpsschools.net"})
f.seek(0)
self.client.post(reverse("eighth_take_attendance", args=[schact1.id]), {"attendance": f})
# Make sure attendance has been marked as taken.
self.assertTrue(EighthScheduledActivity.objects.get(id=schact1.id).attendance_taken)
# Make sure EighthSignup object hasn't been marked absent for user1.
self.assertFalse(EighthSignup.objects.get(user=user1, scheduled_activity=schact1).was_absent)
# Make sure EighthSignup object was marked absent for user2.
self.assertTrue(EighthSignup.objects.get(user=user2, scheduled_activity=schact1).was_absent)
[docs] def test_roster_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.roster_view`."""
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
# Log in, and just load the page
user = self.login()
user.user_type = "teacher"
user.save()
response = self.client.get(reverse("eighth_roster", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
self.assertEqual(scheduled, response.context["scheduled_activity"])
self.assertEqual(0, response.context["viewable_members"].count())
self.assertFalse(response.context["is_sponsor"])
# Now, make this user the sponsor
eighth_sponsor = EighthSponsor.objects.get_or_create(first_name="A", last_name="william", user=user)[0]
scheduled.sponsors.add(eighth_sponsor)
scheduled.save()
response = self.client.get(reverse("eighth_roster", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
self.assertEqual(scheduled, response.context["scheduled_activity"])
self.assertEqual(0, response.context["viewable_members"].count())
self.assertTrue(response.context["is_sponsor"])
[docs] def test_raw_roster_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.raw_roster_view`."""
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
# Log in, and just load the page
user = self.login()
user.user_type = "teacher"
user.save()
response = self.client.get(reverse("eighth_raw_roster", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
self.assertEqual(scheduled, response.context["scheduled_activity"])
self.assertEqual(0, response.context["viewable_members"].count())
[docs] def test_raw_waitlist_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.raw_waitlist_view`."""
# We don't use the waitlist anymore. But that doesn't mean I can test it.
self.make_admin()
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
response = self.client.get(reverse("eighth_raw_waitlist", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
self.assertEqual(0, len(response.context["ordered_waitlist"]))
[docs] def test_generate_roster_pdf(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.generate_roster_pdf`"""
# Make us an admin
self.make_admin()
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
# Make more blocks
block2 = EighthBlock.objects.get_or_create(date=today, block_letter="test")[0]
scheduled2 = EighthScheduledActivity.objects.get_or_create(block=block2, activity=activity)[0]
block3 = EighthBlock.objects.get_or_create(date=today, block_letter="testing")[0]
scheduled3 = EighthScheduledActivity.objects.get_or_create(block=block3, activity=activity)[0]
# Make a user
user = get_user_model().objects.get_or_create(username="2021ttest", first_name="T", last_name="Test")[0]
EighthSignup.objects.create(user=user, scheduled_activity=scheduled)
# Generate the PDF
generate_roster_pdf([scheduled.id, scheduled2.id, scheduled3.id])
# We can't really parse this PDF to make sure it is fine though
[docs] def test_eighth_absences_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.eighth_absences_view`."""
# First, test as a student
user = self.login("2021awilliam")
user.user_type = "student"
user.save()
# Just load the page
response = self.client.get(reverse("eighth_absences"))
self.assertEqual(200, response.status_code)
self.assertEqual(user, response.context["user"])
self.assertEqual(0, len(response.context["absences"]))
# Give this student an eighth absence
# Schedule an activity
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity, attendance_taken=True)[0]
# Make a signup
signup = EighthSignup.objects.create(user=user, scheduled_activity=scheduled, was_absent=True)
# Now, make me an admin and try to find the user
admin = self.make_admin()
response = self.client.get(reverse("eighth_absences", kwargs={"user_id": user.id}))
self.assertEqual(200, response.status_code)
self.assertEqual(user, response.context["user"])
self.assertListEqual([signup], list(response.context["absences"]))
# Try again with this user marked present
signup.was_absent = False
signup.save()
response = self.client.get(reverse("eighth_absences"), data={"user": user.id})
self.assertEqual(200, response.status_code)
self.assertEqual(user, response.context["user"])
self.assertListEqual([], list(response.context["absences"]))
# Assert a redirect if not a student
admin.user_type = "teacher"
admin.save()
response = self.client.get(reverse("eighth_absences"))
self.assertEqual(302, response.status_code)
self.assertEqual(reverse("eighth_admin_dashboard"), response.url)
[docs] def test_email_students_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.email_students_view`."""
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity, attendance_taken=True)[0]
self.login()
response = self.client.get(reverse("eighth_email_students", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(404, response.status_code) # Not an admin
# Make us an admin then just load the page
self.make_admin()
response = self.client.get(reverse("eighth_email_students", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
# Test sending something
response = self.client.post(
reverse("eighth_email_students", kwargs={"scheduled_activity_id": scheduled.id}),
data={"subject": "test email", "body": "this is a test"},
follow=True,
)
self.assertEqual(200, response.status_code)
[docs] def test_teacher_choose_scheduled_activity_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.teacher_choose_scheduled_activity_view`."""
# Make this user a teacher and a sponsor of an eighth period activity
user = self.login()
user.user_type = "teacher"
user.save()
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
sponsor = EighthSponsor.objects.get_or_create(user=user, first_name="a", last_name="william")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
scheduled.sponsors.add(sponsor)
scheduled.save()
# Load the page.
response = self.client.get(reverse("eighth_attendance_choose_scheduled_activity"))
self.assertEqual(200, response.status_code)
self.assertIn(str(block), response.content.decode("UTF-8"))
# Now, POST the block ID to get to the "select activity" page
# But, we are only sponsoring one activity, so we are redirected there
response = self.client.post(
reverse("eighth_attendance_choose_scheduled_activity"),
data={"eighth_attendance_select_scheduled_activity_wizard-current_step": "block", "block-block": block.id},
)
self.assertEqual(302, response.status_code)
self.assertEqual(reverse("eighth_take_attendance", kwargs={"scheduled_activity_id": scheduled.id}), response.url)
# Now, add another activity we're sponsoring
activity2 = EighthActivity.objects.get_or_create(name="Test Activity Two")[0]
scheduled2 = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity2)[0]
scheduled2.sponsors.add(sponsor)
scheduled2.save()
# This should now 200 to the "select activity" page
response = self.client.post(
reverse("eighth_attendance_choose_scheduled_activity"),
data={"eighth_attendance_select_scheduled_activity_wizard-current_step": "block", "block-block": block.id},
)
self.assertEqual(200, response.status_code)
self.assertIn(activity.name, response.content.decode("UTF-8"))
self.assertIn(activity2.name, response.content.decode("UTF-8"))
# Now, POST the first activity
response = self.client.post(
reverse("eighth_attendance_choose_scheduled_activity"),
data={"eighth_attendance_select_scheduled_activity_wizard-current_step": "activity", "activity-activity": activity.id},
)
self.assertEqual(302, response.status_code)
self.assertEqual(reverse("eighth_take_attendance", kwargs={"scheduled_activity_id": scheduled.id}), response.url)
# What about "show past blocks this year"?
yesterday = (timezone.localtime() - timezone.timedelta(days=1)).date()
block_old = EighthBlock.objects.get_or_create(date=yesterday, block_letter="A")[0]
# block_old should not show in this list because it is not "show all blocks"
response = self.client.get(reverse("eighth_attendance_choose_scheduled_activity"))
self.assertEqual(200, response.status_code)
self.assertIn(str(block), response.content.decode("UTF-8"))
self.assertNotIn(str(block_old), response.content.decode("UTF-8"))
# Now, try show_all_blocks = 1 and both blocks should show
response = self.client.get(reverse("eighth_attendance_choose_scheduled_activity"), data={"show_all_blocks": True})
self.assertEqual(200, response.status_code)
self.assertIn(str(block), response.content.decode("UTF-8"))
self.assertIn(str(block_old), response.content.decode("UTF-8"))
# Test default_activity
# response = self.client.get(reverse("eighth_attendance_choose_scheduled_activity"),
# data={"eighth_attendance_select_scheduled_activity_wizard-current_step": "activity",
# "block-block": block.id, "default_activity": activity.id})
# self.assertEqual(200, response.status_code)
# self.assertIn(str(activity), response.content.decode("UTF-8"))
[docs] def test_attendance_export_csv(self):
"""Tests the "export CSV" function in :func:`~intranet.apps.eighth.views.attendance.take_attendance_view`."""
# Make this user a teacher and a sponsor of an eighth period activity
user = self.login()
user.user_type = "teacher"
user.save()
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A")[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
sponsor = EighthSponsor.objects.get_or_create(user=user, first_name="a", last_name="william")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
scheduled.sponsors.add(sponsor)
scheduled.save()
# Get a CSV - should be empty because there are no signups (yet)
response = self.client.get(reverse("eighth_admin_export_attendance_csv", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
# Parse CSV - should be empty
raw_output = response.content.decode("UTF-8").split("\n")
reader = csv.DictReader(raw_output)
self.assertEqual(0, len(list(reader)))
self.assertEqual(
[
"Block",
"Activity",
"Name",
"FCPS ID",
"Student ID",
"Grade",
"Email",
"Locked",
"Rooms",
"Sponsors",
"Attendance Taken",
"Present",
"Had Pass",
],
reader.fieldnames,
)
# Add a student signup
student = get_user_model().objects.get_or_create(username="2021awilliam", first_name="Angela", last_name="William", student_id=1234567)[0]
EighthSignup.objects.get_or_create(user=student, scheduled_activity=scheduled)
response = self.client.get(reverse("eighth_admin_export_attendance_csv", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(200, response.status_code)
raw_output = response.content.decode("UTF-8").split("\n")
reader = csv.DictReader(raw_output)
reader_contents = list(reader)
self.assertEqual(1, len(reader_contents))
self.assertEqual(
{
"Block": str(block),
"Activity": str(activity),
"Name": "William, Angela",
"FCPS ID": "1234567",
"Student ID": str(student.id),
"Grade": str(student.grade.number),
"Email": student.tj_email,
"Locked": "False",
"Rooms": "",
"Sponsors": "william",
"Attendance Taken": "False",
"Present": "N/A",
"Had Pass": "N/A",
},
reader_contents[0],
)
[docs] def test_accept_pass_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.accept_pass_view`."""
# Create an activity and a late signup
user = self.login()
user.user_type = "teacher"
user.save()
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A", locked=True)[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
sponsor = EighthSponsor.objects.get_or_create(user=user, first_name="a", last_name="william")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
scheduled.sponsors.add(sponsor)
scheduled.save()
student = get_user_model().objects.get_or_create(username="2021awilliam", first_name="Angela", last_name="William", student_id=1234567)[0]
signup = EighthSignup.objects.get_or_create(user=student, scheduled_activity=scheduled, after_deadline=True)[0]
# POST to that page and reject the pass
response = self.client.post(reverse("eighth_accept_pass", kwargs={"signup_id": signup.id}), data={"status": "reject"})
self.assertEqual(302, response.status_code) # to attendance page
# Ensure that the signup was marked absent and pass accepted
self.assertTrue(EighthSignup.objects.get(id=signup.id).pass_accepted)
self.assertTrue(EighthSignup.objects.get(id=signup.id).was_absent)
# Now, try accepting
response = self.client.post(reverse("eighth_accept_pass", kwargs={"signup_id": signup.id}), data={"status": "accept"})
self.assertEqual(302, response.status_code) # to attendance page
# Ensure that the signup was NOT marked absent and pass accepted
self.assertTrue(EighthSignup.objects.get(id=signup.id).pass_accepted)
self.assertFalse(EighthSignup.objects.get(id=signup.id).was_absent)
[docs] def test_accept_all_passes_view(self):
"""Tests :func:`~intranet.apps.eighth.views.attendance.accept_all_passes_view`."""
# Very similar to test_accept_pass_view, but we have multiple passes to accept
user = self.login()
user.user_type = "teacher"
user.save()
today = timezone.localtime().date()
block = EighthBlock.objects.get_or_create(date=today, block_letter="A", locked=True)[0]
activity = EighthActivity.objects.get_or_create(name="Test Activity")[0]
sponsor = EighthSponsor.objects.get_or_create(user=user, first_name="a", last_name="william")[0]
scheduled = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity)[0]
scheduled.sponsors.add(sponsor)
scheduled.save()
student1 = get_user_model().objects.get_or_create(username="2021awilliam", first_name="Angela", last_name="William", student_id=1234567)[0]
signup1 = EighthSignup.objects.get_or_create(user=student1, scheduled_activity=scheduled, after_deadline=True)[0]
student2 = get_user_model().objects.get_or_create(username="2022awilliam", first_name="Angela", last_name="William", student_id=1234568)[0]
signup2 = EighthSignup.objects.get_or_create(user=student2, scheduled_activity=scheduled, after_deadline=True)[0]
response = self.client.post(reverse("eighth_accept_all_passes", kwargs={"scheduled_activity_id": scheduled.id}))
self.assertEqual(302, response.status_code) # to attendance page
# Ensure that the signups were NOT marked absent and pass accepted
self.assertTrue(EighthSignup.objects.get(id=signup1.id).pass_accepted)
self.assertFalse(EighthSignup.objects.get(id=signup1.id).was_absent)
self.assertTrue(EighthSignup.objects.get(id=signup2.id).pass_accepted)
self.assertFalse(EighthSignup.objects.get(id=signup2.id).was_absent)