From f9bcd7e0f2ad4b9a41c23f3f60f92afab85d3843 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Sat, 30 Jan 2021 14:10:35 -0600 Subject: [PATCH 01/11] Add prerequisite to Course model. This will allow for specifiying prerequisites for a course. --- coderdojochi/admin.py | 2 ++ .../migrations/0038_course_prerequisite.py | 18 ++++++++++++++++++ coderdojochi/models/course.py | 2 ++ 3 files changed, 22 insertions(+) create mode 100644 coderdojochi/migrations/0038_course_prerequisite.py diff --git a/coderdojochi/admin.py b/coderdojochi/admin.py index 3ea8de17..42b49efc 100644 --- a/coderdojochi/admin.py +++ b/coderdojochi/admin.py @@ -499,6 +499,8 @@ class CourseAdmin(ImportExportMixin, ImportExportActionModelAdmin): "description", ] + filter_horizontal = ["prerequisite"] + prepopulated_fields = { "slug": ("title",), } diff --git a/coderdojochi/migrations/0038_course_prerequisite.py b/coderdojochi/migrations/0038_course_prerequisite.py new file mode 100644 index 00000000..a330857a --- /dev/null +++ b/coderdojochi/migrations/0038_course_prerequisite.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2021-01-30 19:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('coderdojochi', '0037_auto_20210118_1808'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='prerequisite', + field=models.ManyToManyField(blank=True, limit_choices_to={'is_active': True}, to='coderdojochi.Course'), + ), + ] diff --git a/coderdojochi/models/course.py b/coderdojochi/models/course.py index 74b5ac36..7e183877 100644 --- a/coderdojochi/models/course.py +++ b/coderdojochi/models/course.py @@ -58,6 +58,8 @@ class Course(CommonInfo): default=True, ) + prerequisite = models.ManyToManyField("self", symmetrical=False, blank=True, limit_choices_to={"is_active": True}) + def __str__(self): if self.code: return f"{self.code} | {self.title}" From eab552f9687c0577c8019f7faafdbd1844386c45 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 14:20:05 -0600 Subject: [PATCH 02/11] Add get_course_prerequisites_needed method. The new method allows for retrieval of course details for all outstanding prerequisites a student needs for a session. --- coderdojochi/models/session.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/coderdojochi/models/session.py b/coderdojochi/models/session.py index b49ec70e..311598a6 100644 --- a/coderdojochi/models/session.py +++ b/coderdojochi/models/session.py @@ -3,7 +3,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.urls.base import reverse -from django.utils import formats +from django.utils import formats, timezone from django.utils.functional import cached_property from .common import CommonInfo @@ -307,6 +307,21 @@ def get_mentor_capacity(self): else: return int(self.capacity / 2) + def get_course_prerequisites_needed(self, student): + from .order import Order + + student_previous_orders = Order.objects.filter( + student=student, + session__start_date__lt=timezone.now(), + session__course__id__in=self.course.prerequisite.values_list("id"), + ).exclude(check_in=None) + + student_prerequisites_needed = self.course.prerequisite.exclude( + id__in=student_previous_orders.values("session__course__id") + ).distinct("id") + + return student_prerequisites_needed + class PartnerPasswordAccess(CommonInfo): from .user import CDCUser From b630923e460b17109c559c3343e766a668af5a1a Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 14:28:59 -0600 Subject: [PATCH 03/11] Add upcoming prereq sessions to view's context. This adds a dictionary (keyed by Course id) of Session details to the view's context. These session details are for future sessions being held for prerequisites of the current session. This detail facilitates adding prerequisite buttons to the registration form when a student is missing one or more of the prerequisites required. --- coderdojochi/views/guardian/sessions.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/coderdojochi/views/guardian/sessions.py b/coderdojochi/views/guardian/sessions.py index 7cb1a57f..1378c64c 100644 --- a/coderdojochi/views/guardian/sessions.py +++ b/coderdojochi/views/guardian/sessions.py @@ -1,4 +1,5 @@ from django.shortcuts import get_object_or_404 +from django.utils import timezone from django.views.generic import DetailView from ...models import Guardian, Mentor, MentorOrder, Session @@ -18,4 +19,18 @@ def get_context_data(self, **kwargs): id__in=MentorOrder.objects.filter(session=self.object, is_active=True).values("mentor__id") ) + upcoming_prerequisite_sessions = ( + Session.objects.filter( + is_active=True, + is_public=True, + start_date__gte=timezone.now(), + course__id__in=self.object.course.prerequisite.values_list("id"), + ) + .distinct("course__code") + .order_by("course__code", "start_date") + .values("id", "course__id", "course__code") + ) + + context["upcoming_prerequisite_sessions"] = {p["course__id"]: p for p in list(upcoming_prerequisite_sessions)} + return context From adc33755ded1ab50cbab08437bfc6eeb55881c19 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 14:54:44 -0600 Subject: [PATCH 04/11] Update student_register_link tag. The tag will now disable the enroll button when a student does not meet the prerequisites for a session. It will also add buttons for prerequisite course sessions. These buttons will navigate to the registration page of an upcoming session for the prerequisite, if avaialable. --- .../templatetags/coderdojochi_extras.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/coderdojochi/templatetags/coderdojochi_extras.py b/coderdojochi/templatetags/coderdojochi_extras.py index ae96e776..fbef26ce 100644 --- a/coderdojochi/templatetags/coderdojochi_extras.py +++ b/coderdojochi/templatetags/coderdojochi_extras.py @@ -38,6 +38,7 @@ def student_register_link(context, student, session): button_additional_attributes = "" button_msg = "Enroll" button_href = f"href={url}" + prerequisite_needed_buttons = "" if orders.count(): button_modifier = "tertiary" @@ -73,8 +74,45 @@ def student_register_link(context, student, session): message = f"Sorry, this class is limited to {session.gender_limitation}s this time around." button_href = f'data-trigger="hover" data-placement="top" data-toggle="popover" title="" data-content="{message}" data-original-title="{title}" ' + else: + # Get the prerequisites the student still needs to take in order to attend the current session. + course_prerequisites_needed = list(session.get_course_prerequisites_needed(student).values("id", "code")) + + if len(course_prerequisites_needed) > 0: + # When there are outstanding prerequisites, add buttons to navigate to future sessions (if available; disabled otherwise). + for course_prerequisite in course_prerequisites_needed: + needed_course_id = course_prerequisite["id"] + upcoming_prerequisite_sessions = context["upcoming_prerequisite_sessions"] + + if needed_course_id in upcoming_prerequisite_sessions: + session_id = upcoming_prerequisite_sessions[needed_course_id]["id"] + url = reverse("session-detail", kwargs={"pk": session_id}) + button_tag = "a" + button_href = f"href='{url}'" + title = "There is an upcoming session for this prerequisite." + button_additional_attributes = f"data-tooltip aria-haspopup='true' title='{title}'" + else: + button_tag = "span" + button_href = "" + title = "No upcoming session for this prerequisite." + button_additional_attributes = f"data-tooltip aria-haspopup='true' title='{title}' disabled" + + button_modifier = "has-tip" + button_msg = course_prerequisite["code"] + prerequisite_needed_buttons += f"<{button_tag} {button_href} class='button {button_modifier}' {button_additional_attributes}>{button_msg}" + + button_tag = "span" + button_modifier = "has-tip" + button_additional_attributes = "disabled" + button_msg = "Enroll" + title = f"Sorry, this class has prerequisites that have not been met by {student.first_name} yet." + button_href = f'data-tooltip aria-haspopup="true" title="{title}"' + form = f"<{button_tag} {button_href} class='button small {button_modifier}' {button_additional_attributes}>{button_msg}" + if prerequisite_needed_buttons: + form += f"
{prerequisite_needed_buttons}
" + return Template(form).render(context) From 90656d7f47330a2a80e286562cd049ce47205d3e Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 16:31:20 -0600 Subject: [PATCH 05/11] Add prerequisites to class snippet. Will now programmatically list prerequisites associated with a class. --- .../templates/weallcode/snippets/class.html | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/weallcode/templates/weallcode/snippets/class.html b/weallcode/templates/weallcode/snippets/class.html index 945e4dd9..3056b72d 100644 --- a/weallcode/templates/weallcode/snippets/class.html +++ b/weallcode/templates/weallcode/snippets/class.html @@ -6,22 +6,25 @@

{{ session.course.title }}

{{ session.course.description|safe|truncatechars_html:280 }}

- + --> + + {% endif %}
{{ session.start_date|date }}
From f33014743d8c8cc0a77de691e03a3c85a3602e67 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 16:35:25 -0600 Subject: [PATCH 06/11] Fix misspelling of Prerequisites. --- weallcode/templates/weallcode/programs.html | 8 ++++---- weallcode/templates/weallcode/snippets/class.html | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/weallcode/templates/weallcode/programs.html b/weallcode/templates/weallcode/programs.html index 2f969c8d..b61512aa 100644 --- a/weallcode/templates/weallcode/programs.html +++ b/weallcode/templates/weallcode/programs.html @@ -90,7 +90,7 @@

HTML 101

Choose Your Own Adventure

-
Prerequisities
+
Prerequisites
  • None
@@ -116,7 +116,7 @@

CSS 101

Designing Your Adventure

-
Prerequisities
+
Prerequisites
  • HTML 101
@@ -142,7 +142,7 @@

JS 101

Drawing with Javascript

-
Prerequisities
+
Prerequisites
  • None
@@ -167,7 +167,7 @@

PY 101

Robotics with Python

-
Prerequisities
+
Prerequisites
  • None
diff --git a/weallcode/templates/weallcode/snippets/class.html b/weallcode/templates/weallcode/snippets/class.html index 3056b72d..b36a2b1b 100644 --- a/weallcode/templates/weallcode/snippets/class.html +++ b/weallcode/templates/weallcode/snippets/class.html @@ -9,7 +9,7 @@

{{ session.course.title }}

{% if session.course.prerequisite.all.count > 0 %}
-
Prerequisities
+
Prerequisites
    {% for prerequisite in session.course.prerequisite.all %}
  • {{prerequisite.code}}
  • From ce908dc53fe5d477a78255fb1940bd7e17fd30f4 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Tue, 2 Feb 2021 17:37:48 -0600 Subject: [PATCH 07/11] Removed class name from list. This class name was accidentally left in from when I was still playing around with the look of things. --- weallcode/templates/weallcode/snippets/class.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weallcode/templates/weallcode/snippets/class.html b/weallcode/templates/weallcode/snippets/class.html index b36a2b1b..60bed6c3 100644 --- a/weallcode/templates/weallcode/snippets/class.html +++ b/weallcode/templates/weallcode/snippets/class.html @@ -10,7 +10,7 @@

    {{ session.course.title }}

    Prerequisites
    -
      +
        {% for prerequisite in session.course.prerequisite.all %}
      • {{prerequisite.code}}
      • {% endfor %} From ae50fd7799ea6be7bfef4fcddbc324df0d477a54 Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Wed, 17 Feb 2021 22:18:51 -0600 Subject: [PATCH 08/11] Add fixtures for manual testing --- fixtures/05-coderdojochi.course.json | 64 ++++++++++++++++++++------- fixtures/14-coderdojochi.session.json | 34 +++++++------- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/fixtures/05-coderdojochi.course.json b/fixtures/05-coderdojochi.course.json index 0b95e911..9a5cbc35 100644 --- a/fixtures/05-coderdojochi.course.json +++ b/fixtures/05-coderdojochi.course.json @@ -13,7 +13,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 18, - "is_active": true + "is_active": true, + "prerequisite": [] } }, { @@ -30,7 +31,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 18, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -47,7 +49,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 18, - "is_active": true + "is_active": true, + "prerequisite": [] } }, { @@ -56,7 +59,7 @@ "fields": { "created_at": "2017-03-30T16:53:37.340Z", "updated_at": "2017-04-10T23:07:51.518Z", - "code": "JS 101", + "code": "JS 1", "course_type": "WE", "title": "Drawing with Javascript", "slug": "drawing-with-javascript", @@ -64,7 +67,8 @@ "duration": "03:00:00", "minimum_age": 9, "maximum_age": 18, - "is_active": true + "is_active": true, + "prerequisite": [] } }, { @@ -81,7 +85,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -98,7 +103,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -115,7 +121,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -132,7 +139,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -149,7 +157,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -166,7 +175,8 @@ "duration": "03:00:00", "minimum_age": 9, "maximum_age": 17, - "is_active": true + "is_active": true, + "prerequisite": [] } }, { @@ -183,7 +193,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -200,7 +211,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -217,7 +229,8 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] } }, { @@ -234,7 +247,28 @@ "duration": "03:00:00", "minimum_age": 7, "maximum_age": 17, - "is_active": true + "is_active": false, + "prerequisite": [] + } +}, +{ + "model": "coderdojochi.course", + "pk": 15, + "fields": { + "created_at": "2021-02-18T04:07:02.925Z", + "updated_at": "2021-02-18T04:07:02.925Z", + "code": "JS 2", + "course_type": "WE", + "title": "Advanced Drawing With Javascript", + "slug": "advanced-drawing-javascript", + "description": "

        Ever wanted to start building your own game but didn't know where to start? Ever thought it would be cool to have a computer draw a picture or create an animation for you?

        \r\n\r\n

        By the end of the session, students will have the understanding of Canvas using the Javascript programming language. Students will learn how to break down tasks and objects to understand how they are built.

        ", + "duration": "03:00:00", + "minimum_age": 10, + "maximum_age": 18, + "is_active": true, + "prerequisite": [ + 4 + ] } } ] diff --git a/fixtures/14-coderdojochi.session.json b/fixtures/14-coderdojochi.session.json index 229c6458..4855969c 100644 --- a/fixtures/14-coderdojochi.session.json +++ b/fixtures/14-coderdojochi.session.json @@ -91,24 +91,24 @@ "start_date": "2021-03-03T16:00:00Z", "location": 2, "capacity": 20, - "mentor_capacity": null, + "mentor_capacity": 10, "instructor": 1, "cost": "0.00", "minimum_cost": null, "maximum_cost": null, - "additional_info": null, + "additional_info": "", "external_enrollment_url": null, "is_active": true, "is_public": true, - "password": "", - "partner_message": "", + "password": "password", + "partner_message": "

        This is the message that is displayed on the password page

        \r\n

        The password below is password

        ", "announced_date_mentors": null, "announced_date_guardians": null, - "image_url": "classes/0003.jpg", + "image_url": null, "bg_image": "", "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, - "gender_limitation": "Male", + "gender_limitation": null, "override_minimum_age_limitation": null, "override_maximum_age_limitation": null, "online_video_link": null, @@ -127,17 +127,16 @@ "pk": 4, "fields": { "created_at": "2000-01-01T06:00:00Z", - "updated_at": "2000-01-01T06:00:00Z", - "course": 1, + "course": 4, "start_date": "2021-04-04T15:00:00Z", - "location": 1, + "location": 3, "capacity": 20, - "mentor_capacity": null, + "mentor_capacity": 10, "instructor": 1, "cost": "0.00", "minimum_cost": null, "maximum_cost": null, - "additional_info": null, + "additional_info": "", "external_enrollment_url": null, "is_active": true, "is_public": true, @@ -168,25 +167,24 @@ "pk": 5, "fields": { "created_at": "2000-01-01T06:00:00Z", - "updated_at": "2000-01-01T06:00:00Z", - "course": 1, + "course": 15, "start_date": "2021-05-05T16:00:00Z", "location": 3, "capacity": 20, - "mentor_capacity": null, + "mentor_capacity": 10, "instructor": 1, "cost": "0.00", "minimum_cost": null, "maximum_cost": null, - "additional_info": null, + "additional_info": "", "external_enrollment_url": null, "is_active": true, "is_public": true, - "password": "password", - "partner_message": "

        This is the message that is displayed on the password page

        \r\n

        The password below is password

        ", + "password": "", + "partner_message": "", "announced_date_mentors": null, "announced_date_guardians": null, - "image_url": "classes/0001.jpg", + "image_url": null, "bg_image": "", "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, From d6079dbaa5198e8bf0d834fd676d49eb67f359e1 Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Wed, 17 Feb 2021 22:24:23 -0600 Subject: [PATCH 09/11] fix fixture error --- fixtures/14-coderdojochi.session.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fixtures/14-coderdojochi.session.json b/fixtures/14-coderdojochi.session.json index 4855969c..f26fabd3 100644 --- a/fixtures/14-coderdojochi.session.json +++ b/fixtures/14-coderdojochi.session.json @@ -127,6 +127,7 @@ "pk": 4, "fields": { "created_at": "2000-01-01T06:00:00Z", + "updated_at": "2000-01-01T06:00:00Z", "course": 4, "start_date": "2021-04-04T15:00:00Z", "location": 3, @@ -167,6 +168,7 @@ "pk": 5, "fields": { "created_at": "2000-01-01T06:00:00Z", + "updated_at": "2000-01-01T06:00:00Z", "course": 15, "start_date": "2021-05-05T16:00:00Z", "location": 3, From f4d7686ee14b8ba27d360ae5e97f989770050b5b Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Wed, 17 Feb 2021 22:26:43 -0600 Subject: [PATCH 10/11] fixture changes --- fixtures/14-coderdojochi.session.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fixtures/14-coderdojochi.session.json b/fixtures/14-coderdojochi.session.json index f26fabd3..3571f903 100644 --- a/fixtures/14-coderdojochi.session.json +++ b/fixtures/14-coderdojochi.session.json @@ -22,8 +22,8 @@ "partner_message": "", "announced_date_mentors": null, "announced_date_guardians": null, - "image_url": "classes/0001.jpg", - "bg_image": "", + "image_url": null, + "bg_image": null, "mentors_week_reminder_sent": true, "mentors_day_reminder_sent": true, "gender_limitation": "male", @@ -63,8 +63,8 @@ "partner_message": "", "announced_date_mentors": null, "announced_date_guardians": null, - "image_url": "classes/0002.jpg", - "bg_image": "", + "image_url": null, + "bg_image": null, "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, "gender_limitation": null, @@ -105,7 +105,7 @@ "announced_date_mentors": null, "announced_date_guardians": null, "image_url": null, - "bg_image": "", + "bg_image": null, "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, "gender_limitation": null, @@ -145,8 +145,8 @@ "partner_message": "", "announced_date_mentors": null, "announced_date_guardians": null, - "image_url": "classes/0004.jpg", - "bg_image": "", + "image_url": null, + "bg_image": null, "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, "gender_limitation": null, @@ -187,7 +187,7 @@ "announced_date_mentors": null, "announced_date_guardians": null, "image_url": null, - "bg_image": "", + "bg_image": null, "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, "gender_limitation": "female", @@ -228,7 +228,7 @@ "announced_date_mentors": null, "announced_date_guardians": null, "image_url": null, - "bg_image": "", + "bg_image": null, "mentors_week_reminder_sent": false, "mentors_day_reminder_sent": false, "gender_limitation": null, From 7518f7bc3d7098089e4499dda2f96cf05e872553 Mon Sep 17 00:00:00 2001 From: Robert Groves Date: Mon, 22 Feb 2021 20:13:07 -0600 Subject: [PATCH 11/11] Add prereqs to class guardian and public view. --- coderdojochi/templates/guardian/session_detail.html | 12 ++++++++++++ coderdojochi/templates/public/session_detail.html | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/coderdojochi/templates/guardian/session_detail.html b/coderdojochi/templates/guardian/session_detail.html index 6d33be2e..283bf2f9 100644 --- a/coderdojochi/templates/guardian/session_detail.html +++ b/coderdojochi/templates/guardian/session_detail.html @@ -115,6 +115,18 @@

        How To Join Online Class

        Password: {{ object.online_video_meeting_password }}

        {% endif %} + {% if object.course.prerequisite.all.count > 0 %} +
        +
        +
        Prerequisites
        +
          + {% for prerequisite in object.course.prerequisite.all %} +
        • {{prerequisite.code}}
        • + {% endfor %} +
        +
        +
        + {% endif %}
    diff --git a/coderdojochi/templates/public/session_detail.html b/coderdojochi/templates/public/session_detail.html index e692c353..3bb6f524 100644 --- a/coderdojochi/templates/public/session_detail.html +++ b/coderdojochi/templates/public/session_detail.html @@ -54,6 +54,19 @@

    Technical Requirements

    Microphone and Speakers: We highly recommend headphones with a built-in microphone, however any microphone and speakers will work in a quiet room.

    {% endif %} + + {% if object.course.prerequisite.all.count > 0 %} +
    +
    +
    Prerequisites
    +
      + {% for prerequisite in object.course.prerequisite.all %} +
    • {{prerequisite.code}}
    • + {% endfor %} +
    +
    +
    + {% endif %}