diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 64575ba4f..cc599a708 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -7,7 +7,7 @@ from app.models.term import Term from app.models.attachmentUpload import AttachmentUpload from app.logic.fileHandler import FileHandler -from app.logic.utils import selectSurroundingTerms, getFilesFromRequest +from app.logic.utils import selectSurroundingTerms, getFilesFromRequest, selectAllSummerTerms from app.logic.minor import ( changeProposalStatus, createOtherEngagement, @@ -117,13 +117,16 @@ def createSummerExperienceRequest(username): createSummerExperience(username, request.form) flash("Proposal successfully created.", "success") return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals")) + + student = User.get_by_id(username) + year_name = User.rawClassLevel - summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True) + summerTerms = selectAllSummerTerms(g.current_term, student) return render_template("minor/summerExperience.html", selectableTerms = summerTerms, contentAreas = [], - user = User.get_by_id(username), + user = student, ) @minor_bp.route('/cceMinor//getEngagementInformation///', methods=['GET']) diff --git a/app/logic/utils.py b/app/logic/utils.py index 87e5c3f0e..b5239449d 100644 --- a/app/logic/utils.py +++ b/app/logic/utils.py @@ -36,6 +36,43 @@ def selectSurroundingTerms(currentTerm, prevTerms=2, summerOnly=False): return surroundingTerms +def selectAllSummerTerms(currentTerm, student): + """ + Select the summer terms during which a CCE Minor student could be enrolled. + + The user record does not store an admission date, so the admission academic + year is inferred from the student's current class level. The range never + extends beyond the student's inferred final school year; students marked as + ``Graduating`` are treated as fifth-year/fall-graduating students. + """ + classYears = { + "Freshman": 1, + "Sophomore": 2, + "Junior": 3, + "Senior": 4, + "Graduating": 5, + } + + if student.hasGraduated or student.rawClassLevel not in classYears: + return [] + + classYear = classYears[student.rawClassLevel] + # A Spring/Summer term belongs to the academic year that began the prior fall. + academicYearStart = (currentTerm.year if currentTerm.description.startswith("Fall") + else currentTerm.year - 1) + inferredAdmissionYear = academicYearStart - (classYear - 1) + + firstSummer = inferredAdmissionYear + 1 + lastSummer = inferredAdmissionYear + max(4, classYear) - 1 + if currentTerm.description.startswith("Summer"): + lastSummer = max(lastSummer, currentTerm.year) + + return (Term.select() + .where(Term.isSummer, + Term.year >= firstSummer, + Term.year <= lastSummer) + .order_by(Term.termOrder)) + def getStartofCurrentAcademicYear(currentTerm): if ("Summer" in currentTerm.description) or ("Spring" in currentTerm.description): fallTerm = Term.select().where(Term.year==currentTerm.year-1, Term.description == f"Fall {currentTerm.year-1}").get() @@ -97,4 +134,3 @@ def setRedirectTarget(target): return: None """ session["redirectTarget"] = target - diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index e58e4c24a..3b8d1a6c1 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -5,8 +5,6 @@ {{ super() }} - - {% endblock %} @@ -47,7 +45,7 @@

Proposal for Community-Engaged Summer Experience

{% endif %} {% for term in selectableTerms %} - @@ -177,4 +175,4 @@

Experience Information

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/database/test_data.py b/database/test_data.py index bf368e113..856059d9f 100644 --- a/database/test_data.py +++ b/database/test_data.py @@ -494,6 +494,33 @@ "isCurrentTerm": False, "termOrder": "2022-1" }, + { + "id": 11, + "description": "Summer 2018", + "year": 2018, + "academicYear": "2017-2018", + "isSummer": True, + "isCurrentTerm": False, + "termOrder": "2018-2" + }, + { + "id": 12, + "description": "Summer 2019", + "year": 2019, + "academicYear": "2018-2019", + "isSummer": True, + "isCurrentTerm": False, + "termOrder": "2019-2" + }, + { + "id": 13, + "description": "Summer 2020", + "year": 2020, + "academicYear": "2019-2020", + "isSummer": True, + "isCurrentTerm": False, + "termOrder": "2020-2" + }, { "id": 9, "description": "Spring 2024", @@ -1639,4 +1666,4 @@ "isAcademicYear": True } ] -CeltsLabor.insert_many(celtsLabor).on_conflict_replace().execute() \ No newline at end of file +CeltsLabor.insert_many(celtsLabor).on_conflict_replace().execute() diff --git a/tests/code/test_utils.py b/tests/code/test_utils.py index 5a57ee051..778226180 100644 --- a/tests/code/test_utils.py +++ b/tests/code/test_utils.py @@ -1,6 +1,9 @@ import pytest +from types import SimpleNamespace + from app.models import mainDB from app.models.term import Term +from app.logic.utils import selectAllSummerTerms @@ -78,3 +81,48 @@ def test_isFutureTerm(): # current term assert testCurrentTerm.isFutureTerm == False transaction.rollback() + + +@pytest.mark.integration +@pytest.mark.parametrize( + "class_level, current_description, current_year, expected_years", + [ + ("Freshman", "Fall 2090", 2090, [2091, 2092, 2093]), + ("Sophomore", "Summer 2091", 2091, [2090, 2091, 2092]), + ("Senior", "Fall 2090", 2090, [2088, 2089, 2090]), + ], +) +def test_selectAllSummerTerms_uses_estimated_enrollment_window( + class_level, current_description, current_year, expected_years): + with mainDB.atomic() as transaction: + for year in range(2087, 2096): + Term.create(description=f"Summer {year}", + year=year, + academicYear=f"{year - 1}-{year}", + isSummer=True, + isCurrentTerm=False, + termOrder=f"{year}-2") + + student = SimpleNamespace(rawClassLevel=class_level, + hasGraduated=False) + currentTerm = SimpleNamespace(description=current_description, + year=current_year) + + assert expected_years == [ + term.year for term in selectAllSummerTerms(currentTerm, student) + ] + transaction.rollback() + + +def test_selectAllSummerTerms_excludes_graduated_students(): + student = SimpleNamespace(rawClassLevel="Senior", hasGraduated=True) + currentTerm = SimpleNamespace(description="Summer 2090", year=2090) + + assert selectAllSummerTerms(currentTerm, student) == [] + + +def test_selectAllSummerTerms_excludes_unknown_class_levels(): + student = SimpleNamespace(rawClassLevel=None, hasGraduated=False) + currentTerm = SimpleNamespace(description="Summer 2090", year=2090) + + assert selectAllSummerTerms(currentTerm, student) == []