From a2ed6bb6faced41f347b0eb3cf318477c5eb43b5 Mon Sep 17 00:00:00 2001 From: Charles Tang Date: Sat, 16 Mar 2024 23:52:13 -0400 Subject: [PATCH] RecurringSession Model, Tutor Utilities, ManyToMany Rel --- .../__pycache__/settings.cpython-312.pyc | Bin 3288 -> 3288 bytes .../__pycache__/admin.cpython-312.pyc | Bin 541 -> 1232 bytes .../__pycache__/backends.cpython-312.pyc | Bin 1621 -> 2732 bytes .../__pycache__/models.cpython-312.pyc | Bin 4477 -> 9039 bytes .../__pycache__/tests.cpython-312.pyc | Bin 4693 -> 10374 bytes .../__pycache__/urls.cpython-312.pyc | Bin 1396 -> 1754 bytes .../__pycache__/views.cpython-312.pyc | Bin 12336 -> 22530 bytes tutoring_student/admin.py | 13 +- ...ingsession_isrecurring_recurringsession.py | 70 ++++ .../0004_recurringsession_sessions.py | 18 + .../0005_alter_recurringsession_sessions.py | 20 + tutoring_student/models.py | 105 ++++- .../templates/tutoring_student/index.html | 2 +- .../tutoring_student/recurring_details.html | 128 ++++++ .../tutoring_student/session-student.html | 6 +- .../tutoring_student/session-tutor.html | 2 + .../tutoring_student/studentView.html | 4 +- .../tutoring_student/tutorProfile.html | 1 + .../tutoring_student/tutorRecurrings.html | 367 ++++++++++++++++++ .../tutoring_student/tutorUtilities.html | 176 +++++++++ .../templates/tutoring_student/tutorView.html | 8 +- tutoring_student/tests.py | 97 ++++- tutoring_student/urls.py | 3 + tutoring_student/views.py | 150 ++++++- 24 files changed, 1146 insertions(+), 24 deletions(-) create mode 100644 tutoring_student/migrations/0003_tutoringsession_isrecurring_recurringsession.py create mode 100644 tutoring_student/migrations/0004_recurringsession_sessions.py create mode 100644 tutoring_student/migrations/0005_alter_recurringsession_sessions.py create mode 100644 tutoring_student/templates/tutoring_student/recurring_details.html create mode 100644 tutoring_student/templates/tutoring_student/tutorRecurrings.html create mode 100644 tutoring_student/templates/tutoring_student/tutorUtilities.html diff --git a/iridisite/__pycache__/settings.cpython-312.pyc b/iridisite/__pycache__/settings.cpython-312.pyc index be4c2c4d010a9cd7c1754d40bf66c7655bef786f..e59721b2bdec18782acb4555a6d19736610a45aa 100644 GIT binary patch delta 20 acmca1c|(%>G%qg~0}#~Q{JN3*EDr!f;s(3` delta 20 acmca1c|(%>G%qg~0}wcF__&e#EDr!dI|ft$ diff --git a/tutoring_student/__pycache__/admin.cpython-312.pyc b/tutoring_student/__pycache__/admin.cpython-312.pyc index d30f0f7c3bd24a61c274c9371feda1c2729fc2a8..cb9ff566c2428af662bea32f1edc570773262990 100644 GIT binary patch literal 1232 zcmbVL&ubGw6n?Y6lWv+yZG{@7hvu+CdlN)N5G}ES6>NK10?Rftq#HN8b!Hcgcxa*E zwYOfqO7So8VxW>bC$(h}xG1!aF;q7~G-rN1Y@4fw0DiuMTivNo!3cybp zjE+8Z&YvKFBfx-B2yNsd1X#gpsC-lqC{P2e9RSt|Y8j)}@$*>CeOJpW4N+-|%IvG- zD(PKT8sdI>@=ivrYk%iR;aFgk2f;sd75}Si>c6^dVjK%)bBrC!DV#~yL0tE7JBZFC zei5PD4VfREAuBk)D>6qwz(p8bg{UoSR5E^ErKW3ikfZSO3(`m_4Wi~+6b2DlcT8@1 zUgWol=kcQFwPT!y(ziYDRqBVso0-eL4Z>IuN8Dn$!?>C3&^T@Ga9uQ$@B^GnC>Kv3 z-K%dgLRr1B;Zfpjbn?WYE5Q@BRgn}6@ z%exG%++iyDWJc0tH-$NXuiAnjxVQSly3tpavawg|8&Iz7*YZBuvhI}lG;aA(GhS)L zQ9^@lD*rBSWOHD=wCQiBp-=Ns=U|G@MBIwTmGTs&Ui#?d_!tCMd2`rp~{zs;E0+q?k^AlK_$b7!O^gj9mcQT!R0}N{d A>i_@% literal 541 zcmbVIEl&eM5S{JbUYmweQAZF6%$b@xLNI&<1c-vUY(h$RQ?}gRHM@JF5{1R_8wmdc zgKAPfatT2oNL`bV8JagU?>8K zLWF=77>)`Ohgf={CrL;UaMWGOt&6u?UEmyXe2?b%PcH9YF0yd-y?~6|;s9l&#iWQw zE=Pbfa*~=vWyPjR8IMg?opBL&np|s<#799XTV~x>+)3)~BsNN%D|epEmkHyMR>TtJ zyH<3vr-k8JN%4-*hO0Z6SQVy++Z*&$bERqfqNO6b!}X;}dh|dECQ_L%rNVJHf*P00 z<~Y;yWONq0-s?|Gn|5^?@h#;p?EazmY#U>Q&>O71Lt_Aqu@A_jpf-To&F&CZ VUV;2j_J^?gTRDD$bycyWz5u4kb&>!8 diff --git a/tutoring_student/__pycache__/backends.cpython-312.pyc b/tutoring_student/__pycache__/backends.cpython-312.pyc index 304e16dc5213ec8f0958341a6556294f25c0667d..b59c363a56c877b411e8543a7ef74db65ab1cd3a 100644 GIT binary patch literal 2732 zcmc&$&2Jk;6rbJo+K%JIDRvW=By`#&D2tE}mAF_HC^S()A{LM!5zEqQX zE^Z=MK*^z;NG|vY$5!RUe~=3oE{#A+SXJVHIP?}skgA+`GhREJ)FLQCVx)aL^WMBS z?_=I?_s71z1cD(=9+I6nLcj4vd#J(W@I9C`k%>&nMpdaONywB<#g?l|QK?3X5edoY zHZmid$W)2+SZV1MRl$t{Hx}YX*W+5xGrmQW{N$`-68mkVyh>{3Gl?^0U!C_{hx)NO z*E31YEw{|Is2nT~voL8Qf{K!fin1vc6*FQgn^G~dE^Dfv6gC3G8=r<(d3?)bu2aQ^ z=dOT#t8BQIQ^SibFYuB>vDlBR+_^*o!4E zHHX&av9L$cGbUg;j;JD`1=v|fca;THmo&xL26v`TGD&}b&C-ZL+3nbF;xUz(4v&z zBR}RWRzQRK$}(~N_4~^!q`a!PgVUmZgb{nmj|C;695MWOTkc0! z>jrd(uZm(beqVT07$48x%9nU>tW;hxs9l;SY}IwvO7Bw3w7jZlGRSGk;%jEPq$ErL zOJ}X#F9l7>a%<~;GSskS0OT`tkX3rJPcv7Vv)i+KnTg%Z#KV~< znTek=Hy+F!#8L9nSNSjVd%fekz2o10{Bv(^Kb>vnxARYuqx-4!^P^rgaCt9vZ8vr8 zPbBqZe@PD=DDXaJm%#dcJe5E@iS!g!cCJc5HMvlDr227PuNhUM>wZGl0YW^R&y%`- z$206e64Q0lDeF2N1VfL|Ay7mwbHW>05MC|DL)k=s$PqPuluWA`I@%Ur2o?JSNDZ>K z@x|%&zd^=8DQu zy5PzNXt(6qHs;k9Ju7Hi^*^|M5Jj2ct+)1vUTHSA8&3wt_R~YppQZ-)Qe(TRu`_6t0;~6PH_-R#skpKe zlYnYUp-`YhAfO|h2;2~kUgb+puETR0U@~g9wULVpTFf6Y#Scxc?9|}vw64G+H(O}pst(!*( RUPt+1DZ5qr3&Bfx^*28$x!wQ( delta 501 zcmZ1@dXP45_40FK!G&*Jx2|n7?4pM2qZv0;+$N`sVpcU z-cf%;T>62y-1M4>H5UYwC$Hs{{8%zbJ?$2O~f{xF5M# zB_L|#CQEQR27?u$YW&4vlbfGXnv-f*qzcr@2-2+#Bt9@RGBQ48Ful*9^o4_qQDlP6 V7X~2pMTVPEVnWPU1|S7C8348Xab^Gj diff --git a/tutoring_student/__pycache__/models.cpython-312.pyc b/tutoring_student/__pycache__/models.cpython-312.pyc index b58acba9b835a041f25a951e03d8a148a05a628c..9e51a6867cd3bac7f304cf44e027611fbcbb54e5 100644 GIT binary patch literal 9039 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 delta 754 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 EA6V2^!vFvP delta 1324 zcmaKsUrbw79LMiD?d`p{<#zmSX-g?<2Bm}X-v$L)!G$G@t4v@xT)?t>CxuejxtC-h zh!4ihL?d`!G?~ssA4Eu;Ax0m3fNT+>FSjYAOVb!1G{(fl0Zx;>==rtcp9ek3{rvu% z-}#g8J?Fe~Vef?fYgx8(tkL$L)oZpDdpYV`30yGGp`f_(W-rY;oB8sAS$&%ByGv;U zl5J3JV&CB|N0;Q2bOwFI&v8Lpgf^MJ#X*5Co15ey%9u4ZepN#-^{-~#4#TtohL^bS zN-O9*e_nXqe#l~u&FVlc^hLRjj#!%Px&%pR3uwYdqc%5v4Fzb*QbK?CNDU^%tni9O z5%gpEDvnb`MOwG6;e5PqTNCr%B3_`ks>{p^c%p>O#DtN{!IEr+OXZN-LBuNztfQJ8BF#Jrh0~qu`9-u zcAw*V@YDESTl-FLwz++C*ch7FZJzvv$L=X~!+zhwN%9-=tMYOp?PyCn+ER|t=lq`7 zMSN_Pe@R$Z!n9sQ|Er_*l=7#L|Bz8o#C7Q}8Lj^%W0CEs261x*L3#ch{nGyk{Yvs& z_Jb&(D>(tE2GjrofLcHhP{%M3Aq~s~O{4-+IVlTfqMAOc6b%B2l%O=>*#$oHp1S{i%qcIyze1g?t!c4q`Cm70bziLA!CZG3DN?dFg-S*UeUFTA`ktc z?3~cb`ZO=1?T!iG&61l#w?^L{O?z9D-qxLyhPO549Zb0{8qZwX6^Ch8z_qklx?QS*1EEumM;X!wUakN% ld?lU@8!F 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 - diff --git a/tutoring_student/templates/tutoring_student/recurring_details.html b/tutoring_student/templates/tutoring_student/recurring_details.html new file mode 100644 index 0000000..4d923d3 --- /dev/null +++ b/tutoring_student/templates/tutoring_student/recurring_details.html @@ -0,0 +1,128 @@ + + + + + + + {% load static %} + + + Iridium Tutoring | Tutor + + + + {% load static %} + + + {% if user %} + + + {% endif %} + + {% include 'tutoring_student/footer.html' %} + + + diff --git a/tutoring_student/templates/tutoring_student/session-student.html b/tutoring_student/templates/tutoring_student/session-student.html index aa417fd..5aca549 100644 --- a/tutoring_student/templates/tutoring_student/session-student.html +++ b/tutoring_student/templates/tutoring_student/session-student.html @@ -132,12 +132,14 @@

    {% else %}

    - {{tutoringSession.tutor.tutorName}} has signed up for your session and + {{tutoringSession.tutor.tutorName}} {% if tutoringSession.isRecurring %} is your designated recurring tutor. Please contact them + with any questions. + {% else %} has signed up for your session and will reach out soon about the details of your session. If you have any questions, feel free to contact us at iridiumtutoring@gmail.com. + >. {% endif %}

    {% endif %} diff --git a/tutoring_student/templates/tutoring_student/session-tutor.html b/tutoring_student/templates/tutoring_student/session-tutor.html index 4af2ef6..8f99124 100644 --- a/tutoring_student/templates/tutoring_student/session-tutor.html +++ b/tutoring_student/templates/tutoring_student/session-tutor.html @@ -69,6 +69,7 @@

    {{ tutoringSession.student.studentName }}'s registrationTutor: {{ tutoringSession.tutor.tutorName }} - {{tutoringSession.tutor.email}} {% endif %} + {% if not tutoringSession.isRecurring %} {% if not tutoringSession.tutor %}

    You may contact the student via their email of phone number to ask any additional questions about their request. If the student asks to change the time/topic, please contact an administrator to update the session accordingly. To claim this session, press the claim session button below and follow up with an email to the student.

    @@ -85,6 +86,7 @@

    {{ tutoringSession.student.studentName }}'s registration{{tutoringSession.tutor.tutorName}} has signed up for this session.

    {% endif %} + {% endif %} Go Back {% endif %} diff --git a/tutoring_student/templates/tutoring_student/studentView.html b/tutoring_student/templates/tutoring_student/studentView.html index fe8eae5..f18970d 100644 --- a/tutoring_student/templates/tutoring_student/studentView.html +++ b/tutoring_student/templates/tutoring_student/studentView.html @@ -80,8 +80,10 @@

    Upcoming Tutoring Sessions

    {{session.student.studentName}} - {{session.subject}} (Grade {{session.gradeLevel}})
    {% if session.was_in_the_past %} Past + {% elif session.isRecurring %} + Upcoming (Recurring) {% elif session.tutor %} - Upcoming (Tutor Confirmed) + Upcoming (Tutor Confirmed) {% else %} Upcoming (Tutor Pending) {% endif %} diff --git a/tutoring_student/templates/tutoring_student/tutorProfile.html b/tutoring_student/templates/tutoring_student/tutorProfile.html index 306a163..d9e0419 100644 --- a/tutoring_student/templates/tutoring_student/tutorProfile.html +++ b/tutoring_student/templates/tutoring_student/tutorProfile.html @@ -28,6 +28,7 @@ Home Tutors Profile + Utilities diff --git a/tutoring_student/templates/tutoring_student/tutorRecurrings.html b/tutoring_student/templates/tutoring_student/tutorRecurrings.html new file mode 100644 index 0000000..fd7c84e --- /dev/null +++ b/tutoring_student/templates/tutoring_student/tutorRecurrings.html @@ -0,0 +1,367 @@ + + + + + + + {% load static %} + + + Iridium Tutoring | Tutor + + + + {% load static %} + + + {% if user %} +
    +

    Your Current Recurrings

    +
    +
    + + {% for recurring in recurringSessions %} + {% if not recurring.was_in_the_past %} +
    +
    +
    {{recurring.student.studentName}} - {{recurring.subject}}
    +

    + {{recurring.dayOfWeek}}s at {{recurring.time}} for + {{recurring.duration}} hours
    + From: {{recurring.startDate}} +
    + To: {{recurring.endDate}} +
    + Additional Notes:
    + {{recurring.description}} +

    + More Information +
    +
    + {% endif %} + {% endfor %} +
    +
    + + +
    +

    Your Past Recurrings

    +
    +
    + + {% for recurring in recurringSessions %} + {% if recurring.was_in_the_past %} +
    +
    +
    {{recurring.student.studentName}} - {{recurring.subject}}
    +

    + {{recurring.dayOfWeek}}s at {{recurring.time}} for + {{recurring.duration}} hours
    + From: {{recurring.startDate}} +
    + To: {{recurring.endDate}} +
    + Additional Notes:
    + {{recurring.description}} +

    +
    +
    + {% endif %} + {% endfor %} +
    +
    + + +
    +
    +

    Create A Recurring

    +
    + +
    +
    +
    + + {% csrf_token %} + +
    + + +
    + If the student name does not exist, please ask them to register for at least one session with Iridium Tutoring. +
    +
    + +
    + + +
    + To register for multiple days of the week, you must submit a separate recurrings form for each day. +
    +
    + +
    + + +
    + Even if your times change by day, place an approximate time for our records. +
    +
    + +
    + + +
    + Most commonly, sessions are 0.5, 1, or 1.5 hours. +
    +
    + +
    + + +
    + +
    + + +
    + Please ensure your end date is after your start date. +
    +
    + +
    + + +
    + +
    + + +
    + Please specify the specific topic to be covered. (i.e. SAT + Math - Right Angle Geometry) +
    +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + +
    +
    +
    +
    +
    + + {% endif %} + + + + {% include 'tutoring_student/footer.html' %} + + + diff --git a/tutoring_student/templates/tutoring_student/tutorUtilities.html b/tutoring_student/templates/tutoring_student/tutorUtilities.html new file mode 100644 index 0000000..ff53b19 --- /dev/null +++ b/tutoring_student/templates/tutoring_student/tutorUtilities.html @@ -0,0 +1,176 @@ + + + + + + + {% load static %} + + + Iridium Tutoring | Tutor + + + + {% load static %} + + + {% if user %} +
    +

    Tutor Utilities

    +
    +
    + +
    + Discord Logo +
    +
    Discord Server
    +

    + Internal server for communication between board of directors, + staff, and tutors. Strictly invite-only. +

    + Server Invite +
    +
    + +
    + Iridium Logo +
    +
    Information Hub
    +

    + Tutorials and How-To information for tutors, onboarding, + staff, board of directors, etc. +

    + Take Me There +
    +
    + + +
    + Iridium Logo +
    +
    Recurrings
    +

    + Create, read, update, destroy (CRUD) your upcoming/past recurring sessions. +

    + Go To Tool +
    +
    +
    +
    + {% endif %} + + {% include 'tutoring_student/footer.html' %} + + + diff --git a/tutoring_student/templates/tutoring_student/tutorView.html b/tutoring_student/templates/tutoring_student/tutorView.html index 56faea9..1441cc1 100644 --- a/tutoring_student/templates/tutoring_student/tutorView.html +++ b/tutoring_student/templates/tutoring_student/tutorView.html @@ -70,6 +70,7 @@ Tutors {% if user %} Profile + Utilities {% endif %} @@ -112,7 +113,7 @@

    - +

    Your Upcoming Tutoring Sessions

    @@ -124,6 +125,9 @@

    Your Upcoming Tutoring Sessions

    {{session.student.studentName}} - {{session.subject}} (Grade {{session.gradeLevel}})
    + {% if session.isRecurring %} + Recurring + {% endif %}
    {{session.date}} - {{session.time}} ({{session.duration}} hours) @@ -140,7 +144,7 @@

    Available Tutoring Sessions

    {% if tutoringSessionList %}
    {% for session in tutoringSessionList %} - {% if not session.was_in_the_past %} + {% if not session.was_in_the_past and not session.isRecurring %}
    {{session.student.studentName}} - {{session.subject}} (Grade {{session.gradeLevel}})
    diff --git a/tutoring_student/tests.py b/tutoring_student/tests.py index 1062a13..f47638d 100644 --- a/tutoring_student/tests.py +++ b/tutoring_student/tests.py @@ -7,6 +7,9 @@ class TutoringSessionModelTest(TestCase): def setUp(self): + """ + Set up a tutoring session and form data for testing. + """ self.form_data = { "date": timezone.now().date(), "time": timezone.now().time(), @@ -18,13 +21,42 @@ def setUp(self): "name": " John Doe ", "email": "johndoe@gmail.com ", } + time = timezone.now() + self.session = TutoringSession( + date=time.date(), + time=timezone.now().time(), + duration=1, + subject="Math", + description="Help with math", + gradeLevel="12", + preferredPlatform="Zoom", + student=Student(studentName="John Doe", email="johndoe@gmail.com"), + ) def test_was_in_past_with_future_date(self): """ was_in_the_past() returns False for sessions in the future. """ time = timezone.now() + datetime.timedelta(days=30) - future_session = TutoringSession( + future_session = self.session + future_session.date = time.date() + self.assertIs(future_session.was_in_the_past(), False) + + def test_was_in_past_with_past_date(self): + """ + was_in_the_past() returns True for sessions in the past. + """ + time = timezone.now() - datetime.timedelta(days=1) + past_session = self.session + past_session.date = time.date() + self.assertIs(past_session.was_in_the_past(), True) + + def test_is_today(self): + """ + is_today() returns True for sessions that are today. + """ + time = timezone.now() + today_session = TutoringSession( date=time.date(), time=timezone.now().time(), duration=1, @@ -34,14 +66,14 @@ def test_was_in_past_with_future_date(self): preferredPlatform="Zoom", student=Student(studentName="John Doe", email="johndoe@gmail.com"), ) - self.assertIs(future_session.was_in_the_past(), False) + self.assertIs(today_session.is_today(), True) - def test_was_in_past_with_past_date(self): + def test_has_tutor(self): """ - was_in_the_past() returns True for sessions in the past. + has_tutor() returns True for sessions that have a tutor object in its field. """ - time = timezone.now() - datetime.timedelta(days=1) - past_session = TutoringSession( + time = timezone.now() + session = TutoringSession( date=time.date(), time=timezone.now().time(), duration=1, @@ -50,8 +82,9 @@ def test_was_in_past_with_past_date(self): gradeLevel="12", preferredPlatform="Zoom", student=Student(studentName="John Doe", email="johndoe@gmail.com"), + tutor=Tutor(tutorName="John Doe", email="johndoe@yahoo.com"), ) - self.assertIs(past_session.was_in_the_past(), True) + self.assertIs(session.has_tutor(), True) def test_register_session_form_not_from_index(self): """ @@ -83,3 +116,53 @@ def test_register_session_form_from_index(self): ), 1, ) + + +class RecurringSessionModelTest(TestCase): + def setUp(self): + """ + Set up a recurring session for testing. + """ + time = timezone.now() + student = Student(studentName="John Student", email="johndoe@gmail.com") + tutor = Tutor(tutorName="John Tutor", email="johndoe@gmail.com") + self.recurring = RecurringSession( + student=student, + tutor=tutor, + dayOfWeek="Monday", + time=time.time(), + startDate=time.date(), + endDate=time.date() + datetime.timedelta(days=100), + duration=1, + subject="Math", + description="Help with math", + gradeLevel="12", + preferredPlatform="Zoom", + ) + + def test_generate_sessions(self): + """ + generate_sessions() returns True for sessions that have been generated. + """ + self.assertIs(self.recurring.generate_sessions(), True) + self.assertIs(self.recurring.sessions[0].isRecurring, True) + print(self.recurring.sessions) + + def test_sort_session(self): + """ + sort_session() returns True for sessions that have been sorted. + """ + self.recurring.generate_sessions() + self.assertIs(self.recurring.sort_session(), True) + for i in range(len(self.recurring.sessions) - 1): + self.assertLessEqual( + self.recurring.sessions[i].date, self.recurring.sessions[i + 1].date + ) + + def test_was_in_past(self): + """ + was_in_the_past() returns True for sessions that have ended. + """ + self.assertIs(self.recurring.was_in_the_past(), False) + self.recurring.endDate = timezone.now().date() - datetime.timedelta(days=1) + self.assertIs(self.recurring.was_in_the_past(), True) diff --git a/tutoring_student/urls.py b/tutoring_student/urls.py index ca703cd..ca6d535 100644 --- a/tutoring_student/urls.py +++ b/tutoring_student/urls.py @@ -14,5 +14,8 @@ path("studentPortal/session-student//", views.session_details_student, name="session_details_student"), path("tutorPortal/session-tutor//", views.session_details_tutor, name="session_details_tutor"), path("tutorPortal/profile", views.tutorProfile, name="tutorProfile"), + path("tutorPortal/utilities", views.tutorUtilities, name="tutorUtilities"), + path("tutorPortal/utilities/recurrings", views.tutorRecurrings, name="tutorRecurrings"), + path("tutorPortal/utilities/recurrings//", views.recurring_details, name="recurring_details"), path("session-confirmation/", views.sessionConfirmation, name="register_session"), ] diff --git a/tutoring_student/views.py b/tutoring_student/views.py index dc12dc6..b9f9bd2 100644 --- a/tutoring_student/views.py +++ b/tutoring_student/views.py @@ -176,6 +176,64 @@ def send_confirmation_email(context, email): ) +def register_recurring_session(request, data): + """Register recurring session given form data + + Args: + request (Django HTTP request): passed from previous view + data (dict): form data + """ + + # Check validity of form data (Start/End data, nullity) + if ( + data["startDate"] == "" + or data["tutor"] == None + or data["endDate"] == "" + or data["student"] == None + or data["dayOfWeek"] == "" + or data["time"] == "" + or data["duration"] == "" + or data["subject"] == "" + or data["description"] == "" + or data["gradeLevel"] == "" + or data["platform"] == "" + ): + raise Http404("Data fields missing.") + if data["startDate"] >= data["endDate"]: + raise Http404("Start date must be before end date.") + + try: + # Create recurring session object + recurring = RecurringSession( + student=data["student"], + tutor=data["tutor"], + dayOfWeek=data["dayOfWeek"], + time=data["time"], + duration=data["duration"], + startDate=data["startDate"], + endDate=data["endDate"], + subject=data["subject"], + description=data["description"], + gradeLevel=data["gradeLevel"], + preferredPlatform=data["platform"], + ) + + # Check if recurring session already exists + if not RecurringSession.objects.filter( + student=data["student"], + dayOfWeek=data["dayOfWeek"], + time=data["time"], + startDate=data["startDate"], + ).first(): + recurring.save() # Save recurring session so Many-To-Many relationship can be established + recurring.generate_sessions() + return recurring + return None + except Exception as e: + print(e) + raise Http404("There was an error generating the sessions.") + + #### VIEWS #### @@ -391,6 +449,96 @@ def tutorProfile(request): return render(request, "tutoring_student/tutorProfile.html", context) +@user_passes_test(check_tutor) +def tutorUtilities(request): + """Tutor utilities view + + Args: + request (Django HTTP request): passed from previous view + + Returns: + tutorUtilities: render tutorUtilities.html with context + """ + try: + tutor = get_tutor(request, request.user) + except: + raise Http404("You are not authorized to access this page.") + context = {"tutor": tutor, "user": tutor != None} + return render(request, "tutoring_student/tutorUtilities.html", context) + + +@user_passes_test(check_tutor) +def tutorRecurrings(request): + """Tutor recurrings view + + Args: + request (Django HTTP request): passed from previous view + + Returns: + tutorRecurrings: render tutorRecurrings.html with context + """ + + try: + tutor = get_tutor(request, request.user) + except: + raise Http404("You are not authorized to access this page.") + + # Get form data from create recurrings + if request.method == "POST": + name = request.POST.get("studentName", "").strip() + data = { + "student": Student.objects.get(studentName=name), + "tutor": tutor, + "dayOfWeek": request.POST.get("dayOfWeek", "").strip(), + "time": request.POST.get("time", "").strip(), + "duration": request.POST.get("duration", 1).strip(), + "startDate": request.POST.get("startDate", "").strip(), + "endDate": request.POST.get("endDate", "").strip(), + "subject": request.POST.get("subject", "").strip(), + "description": request.POST.get("description", "").strip(), + "gradeLevel": request.POST.get("gradeLevel", "").strip(), + "platform": request.POST.get("platform", "").strip(), + } + try: + recurring = register_recurring_session(request, data) + except Exception as e: + print(e) + raise Http404("There was an error creating the recurring session.") + + recurringSessions = RecurringSession.objects.filter(tutor=tutor) + allStudents = Student.objects.all() + context = { + "tutor": tutor, + "user": tutor != None, + "recurringSessions": recurringSessions, + "allStudents": allStudents, + } + return render(request, "tutoring_student/tutorRecurrings.html", context) + + +@user_passes_test(check_tutor) +def recurring_details(request, recurring_id): + """Recurring details view + + Args: + request (Django HTTP request): passed from previous view + recurring_id (int): foreign key for recurring session object + + Raises: + Http404: if recurring session does not exist or tutor is not authorized + + Returns: + recurring_details: render recurring_details.html with context + """ + try: + tutor = get_tutor(request, request.user) + except: + raise Http404("You are not authorized to access this page.") + recurring = get_object_or_404(RecurringSession, pk=recurring_id) + context = {"recurring": recurring, "tutor": tutor, "user": tutor != None} + return render(request, "tutoring_student/recurring_details.html", context) + + def sessionConfirmation(request): """ Session confirmation view @@ -457,7 +605,7 @@ def sessionConfirmation(request): # Send confirmation email and save tutoring session object else: try: - send_confirmation_email(context, email) + # send_confirmation_email(context, email) t.save() except: context["error_message"] = (