diff --git a/docker/prod-values.yml b/docker/prod-values.yml index adfeb16fc6..4100c79fd2 100644 --- a/docker/prod-values.yml +++ b/docker/prod-values.yml @@ -178,7 +178,7 @@ cronjob: cpu: 25m memory: 32Mi - name: course-cleanup - schedule: "1 3 * 1,4,7,9 1" + schedule: "21 20 * * 5" command: ["/scripts/management_command.sh"] args: ["db_cleanup", "course"] resources: @@ -189,7 +189,7 @@ cronjob: cpu: 25m memory: 128Mi - name: seenreg-cleanup - schedule: "1 3 * 1,4,7,9 2" + schedule: "1 23 * * 5" command: ["/scripts/management_command.sh"] args: ["db_cleanup", "seenreg"] resources: @@ -200,7 +200,7 @@ cronjob: cpu: 25m memory: 128Mi - name: notice-cleanup - schedule: "1 3 * * 3" + schedule: "1 21 * * 5" command: ["/scripts/management_command.sh"] args: ["db_cleanup", "notice"] resources: @@ -211,7 +211,7 @@ cronjob: cpu: 25m memory: 512Mi - name: linkvisit-cleanup - schedule: "1 3 * * 4" + schedule: "1 22 * * 5" command: ["/scripts/management_command.sh"] args: ["db_cleanup", "linkvisit"] resources: @@ -231,7 +231,7 @@ environmentVariables: - name: BOOKSTORE_ENV value: PROD - name: BOOK_POOL_SIZE - value: "25" + value: "40" - name: BOOK_TIMEOUT value: "15" diff --git a/myuw/dao/textbook.py b/myuw/dao/textbook.py index 66f4eee0b0..28b90bd4b8 100644 --- a/myuw/dao/textbook.py +++ b/myuw/dao/textbook.py @@ -5,7 +5,8 @@ encapsulates the interactions with the Bookstore web service. """ -from uw_bookstore import Bookstore +from uw_bookstore.digital_material import IACoursesStatus as Bookstore +from myuw.dao.pws import get_regid_of_current_user bookstore = Bookstore() @@ -22,3 +23,15 @@ def get_order_url_by_schedule(schedule): returns a link to the bookstore ordering page for a given schedule """ return bookstore.get_url_for_schedule(schedule) + + +def get_iacourse_status(request, term): + """ + MUWM-5272 + returns a TermIACourse object if has data, otherwith return None + """ + terms_iacourses = bookstore.get_iacourse_status( + get_regid_of_current_user(request) + ) + key = "{}{}".format(term.quarter, term.year) + return terms_iacourses.get(key) diff --git a/myuw/data/resource_link_import.csv b/myuw/data/resource_link_import.csv index 122651237a..2c4ddb37a1 100644 --- a/myuw/data/resource_link_import.csv +++ b/myuw/data/resource_link_import.csv @@ -65,7 +65,7 @@ Campus Life,Campus Retail,all,,,http://www.bookstore.washington.edu/student_facu Campus Life,Campus Safety,all,,,https://www.washington.edu/safety/,Campus Safety,https://www.uwb.edu/safety,Campus Safety Department,https://www.tacoma.uw.edu/uwt/fa/safety/,Campus Safety & Security,no Campus Life,Campus Safety,all,http://www.washington.edu/safecampus/,SafeCampus,,,,,,,no Campus Life,Campus Safety,all,,,https://www.ehs.washington.edu/,Environmental Health & Safety,https://www.uwb.edu/safety/ehs-emergency/environment-health-safety,Environmental Health & Safety,https://www.tacoma.uw.edu/fa/environmental-health-safety,Environmental Health & Safety,no -Campus Life,Campus Safety,all,https://cspc.admin.uw.edu/mychem,MyChem,,,,,,,no +Campus Life,Campus Safety,all,https://mychem.ehs.washington.edu/,MyChem,,,,,,,no Campus Life,Campus Safety,all,,,https://www.washington.edu/uwem/,UW Emergency Management,https://www.uwb.edu/emergency,UW Bothell Emergency Page,https://www.tacoma.uw.edu/fa/safety/emergency-preparedness,UW Tacoma Emergency Response,no Campus Life,Campus Safety,all,https://www.washington.edu/titleix/report/,Make a Title IX report,,,,,,,no Campus Life,Getting Around Campus,all,,,http://www.washington.edu/maps/,Campus Map,https://www.uwb.edu/wp-content/uploads/2023/09/uw-bothell-campus-map.pdf,Campus Map,http://www.tacoma.uw.edu/campus-map/campus-map,Campus Map,no @@ -78,7 +78,7 @@ Campus Life,Health & Wellness,all,https://wellbeing.uw.edu/,Husky Health & Well- Campus Life,Health & Wellness,all,,,http://www.washington.edu/counseling/,Counseling Center,https://www.uwb.edu/student-affairs/counseling,Counseling Services,https://www.tacoma.uw.edu/uwt/caps,Counseling & Psychological Services,no Campus Life,Health & Wellness,all,,,https://wellbeing.uw.edu/unit/hall-health/,Hall Health Center,https://www.uwb.edu/arc/hawrc,Health and Wellness Resource Center,https://www.tacoma.uw.edu/sh,Student Health Services (SHS),no Campus Life,Health & Wellness,all,,,https://wellbeing.uw.edu/unit/livewell/,LiveWell Office,,,,,no -Campus Life,Health & Wellness,all,,,http://www.washington.edu/ima/,Recreational Sports Programs (IMA),http://www.uwb.edu/recwell,Recreation and Wellness,http://www.tacoma.uw.edu/uwy/,Recreation & Fitness,no +Campus Life,Health & Wellness,all,,,http://www.washington.edu/ima/,Recreational Sports Programs (IMA),https://www.uwb.edu/arc/,Activities and Recreation Center(ARC),http://www.tacoma.uw.edu/uwy/,Recreation & Fitness,no Campus Life,Health & Wellness,all,https://www.washington.edu/wholeu/,Whole U (for staff and faculty),,,,,,,no Campus Life,Housing & Dining,all,,,https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx,Husky Card Account,https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx,Husky Card Account,http://apps.tacoma.uw.edu/dawgdollars/,Tacoma Dawg Dollars Account,no Campus Life,Housing & Dining,all,,,https://www.hfs.washington.edu/dining/,Campus Dining,http://www.uwb.edu/food,Campus Dining,https://www.tacoma.uw.edu/uwt/fa/facilities/food-services,Campus Dining,no @@ -90,7 +90,6 @@ Campus Life,Rules and Regulations,all,http://www.washington.edu/students/reg/fer Campus Life,Rules and Regulations,all,,,https://ap.washington.edu/ormp/,Office of Research Misconduct Proceedings,https://www.uwb.edu/student-affairs/studentconduct/student-guide,Student Guide to Academic Integrity,https://ap.washington.edu/ormp/,Office of Research Misconduct Proceedings,no Campus Life,Rules and Regulations,all,,,http://www.washington.edu/cssc/student-conduct-overview/student-code-of-conduct/,Student Conduct Code,https://www.uwb.edu/student-affairs/studentconduct,Student Conduct and Responsibility,https://www.tacoma.uw.edu/student-conduct/student-code,Student Conduct Code,no Campus Life,Support Services,all,https://www.washington.edu/circle/,Center for International Relations & Cultural Leadership Exchange (CIRCLE),,,,,,,no -Campus Life,Support Services,all,,,,,https://www.uwb.edu/cie,Center for International Studies,,,no Campus Life,Support Services,all,http://www.fiuts.org/,Foundation for International Understanding Through Students (FIUTS),,,,,,,no Campus Life,Support Services,all,https://grad.uw.edu/current-students/student-success/,Graduate School Student Success,,,,,,,no Campus Life,Support Services,all,,,http://www.washington.edu/admin/dso/uw-resources.html,Disability Resources,https://www.uwb.edu/student-affairs/drs,Disability Resources for Students (DRS),http://www.tacoma.uw.edu/drsuwt,Disability Resources for Students,no @@ -121,7 +120,7 @@ Events and Activities,Stay Connected,all,,,http://www.youtube.com/user/uwhuskies Events and Activities,Stay Connected,all,,,https://www.instagram.com/uofwa/,UW Instagram,https://instagram.com/UW_BOTHELL,UW Bothell Instagram,https://www.instagram.com/uwtacoma,UW Tacoma Instagram,no Events and Activities,Stay Connected,all,,,https://twitter.com/UW,UW Twitter,https://twitter.com/UWBothell,UW Bothell Twitter,https://twitter.com/uwtacoma,UW Tacoma Twitter,no Events and Activities,Stay Connected,all,,,https://www.linkedin.com/company/university-of-washington,UW LinkedIn,https://www.linkedin.com/groups/University-Washington-Bothell-92858?home=&gid=92858,UW Bothell LinkedIn,https://www.linkedin.com/school/university-of-washington-tacoma/,UW Tacoma LinkedIn,no -Events and Activities,Student Clubs and Organizations,all,,,https://hub.washington.edu/sao/rso-directory/,Registered Student Organizations (RSO) Directory,http://www.bothell.washington.edu/sea/clubs/join,Club Directory,https://dawgden.tacoma.uw.edu/Organizations,UW Tacoma Clubs and Organizations,no +Events and Activities,Student Clubs and Organizations,all,,,https://hub.washington.edu/sao/rso-directory/,Registered Student Organizations (RSO) Directory,https://uwb.presence.io/organizations,Club Directory,https://dawgden.tacoma.uw.edu/Organizations,UW Tacoma Clubs and Organizations,no Libraries,Library Resources,all,,,http://lib.washington.edu/,UW Libraries,http://library.uwb.edu/,UW Bothell Library,http://www.tacoma.uw.edu/library,UW Tacoma Library,no Libraries,Library Resources,all,http://search.lib.uw.edu/account,Your Library Account,,,,,,,no Libraries,Library Resources,all,http://guides.lib.uw.edu/az.php,Articles and Research Databases,,,,,,,no @@ -165,7 +164,6 @@ Research,"Research Centers, Shared Facilities, and Equipment",all,https://escien Research,Research Data and Publishing,all,https://www.lib.washington.edu/services/faculty,Getting Started with Library Research,,,,,,,no Research,Research Data and Publishing,all,https://www.lib.washington.edu/dataservices/tools/services,Tools for Data-Related Research,,,,,,,no Research,Research Data and Publishing,all,https://guides.lib.uw.edu/research/dmg,Research Data Services,,,,,,,no -Research,Research Data and Publishing,all,https://hsl.uw.edu/trail/,Translational Research and Information Lab (TRAIL),,,,,,,no Research,Research Data and Publishing,all,,,https://www.lib.washington.edu/openscholarship,"Open Scholarship Commons: Tools, Services, Consultations",https://guides.lib.uw.edu/bothell/digitalscholarship,Digital Scholarship,https://guides.lib.uw.edu/tacdigitalscholarship,Digital Scholarship,no Research,Research Data and Publishing,all,https://guides.lib.uw.edu/research/spoa,Scholarly Publishing and Open Access,,,,,,,no Research,Technical Support,all,https://itconnect.uw.edu/help/,UW-IT Technical Support,,,,,,,no diff --git a/myuw/management/commands/db_cleanup.py b/myuw/management/commands/db_cleanup.py index a7c5cfe323..44747b6cc6 100644 --- a/myuw/management/commands/db_cleanup.py +++ b/myuw/management/commands/db_cleanup.py @@ -14,7 +14,7 @@ from uw_sws import sws_now, SWS_TIMEZONE from myuw.models import ( VisitedLinkNew, SeenRegistration, UserNotices, UserCourseDisplay) -from myuw.dao.term import get_term_by_date, get_term_before +from myuw.dao.term import get_term_by_date, get_term_before, get_term_after from myuw.util.settings import get_cronjob_recipient, get_cronjob_sender from myuw.logger.timer import Timer @@ -65,11 +65,19 @@ def deletion(self, ids_to_delete, queryf): ["{}@uw.edu".format(get_cronjob_recipient())]) raise CommandError(msg) + def get_cur_term(self): + comparison_date = sws_now().date() + term = get_term_by_date(comparison_date) + # Match MyUW quarter switchS + if comparison_date > term.grade_submission_deadline.date(): + return get_term_after(term) + return term + def course_display(self): # clean up after one year timer = Timer() queryf = "DELETE FROM user_course_display_pref WHERE id IN ({})" - term = get_term_by_date(sws_now().date()) + term = self.get_cur_term() y = term.year - 1 q = term.quarter qset = UserCourseDisplay.objects.filter(year=y, quarter=q) @@ -88,7 +96,7 @@ def notice_read(self): # clean up after 180 days timer = Timer() queryf = "DELETE FROM myuw_mobile_usernotices WHERE id IN ({})" - cut_off_dt = self.get_cut_off_date() + cut_off_dt = self.get_cut_off_date(90) qset = UserNotices.objects.filter(first_viewed__lt=cut_off_dt) if qset.exists(): ids_to_delete = qset.values_list('id', flat=True) @@ -105,7 +113,7 @@ def registration_seen(self): # clean up previous quarters' timer = Timer() queryf = "DELETE FROM myuw_mobile_seenregistration WHERE id IN ({})" - term = get_term_before(get_term_by_date(sws_now().date())) + term = get_term_before(self.get_cur_term()) qset = SeenRegistration.objects.filter( year=term.year, quarter=term.quarter) if qset.exists(): @@ -123,7 +131,7 @@ def link_visited(self): # clean up after 180 days timer = Timer() queryf = "DELETE FROM myuw_visitedlinknew WHERE id IN ({})" - cut_off_dt = self.get_cut_off_date() + cut_off_dt = self.get_cut_off_date(90) qset = VisitedLinkNew.objects.filter(visit_date__lt=cut_off_dt) if qset.exists(): ids_to_delete = qset.values_list('id', flat=True) diff --git a/myuw/resources/book/file/uw/iacourse_status.json_regid_12345678901234567890123456789012 b/myuw/resources/book/file/uw/iacourse_status.json_regid_12345678901234567890123456789012 new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/myuw/resources/book/file/uw/iacourse_status.json_regid_12345678901234567890123456789012 @@ -0,0 +1 @@ +{} diff --git a/myuw/resources/book/file/uw/iacourse_status.json_regid_9136CCB8F66711D5BE060004AC494FFE b/myuw/resources/book/file/uw/iacourse_status.json_regid_9136CCB8F66711D5BE060004AC494FFE new file mode 100644 index 0000000000..d0994d791a --- /dev/null +++ b/myuw/resources/book/file/uw/iacourse_status.json_regid_9136CCB8F66711D5BE060004AC494FFE @@ -0,0 +1,76 @@ +[ + { + "Year": 2013, + "Quarter": "spring", + "IACourses": [ + { + "SLN": 13833, + "BookstoreCourseID": 1000111, + "DigitalItems": [ + { + "ISBN": "9781256396362", + "ItemInternalID": 500555, + "OptsOutStatus": false, + "Paid": true, + "Price": 99.00 + } + ] + }, + { + "SLN": 18532, + "BookstoreCourseID": 1000133, + "DigitalItems": [ + { + "ISBN": "9780878935970", + "ItemInternalID": 3000366, + "OptsOutStatus": true, + "Paid": false, + "Price": 96.59 + } + ] + }, + { + "SLN": 13830, + "BookstoreCourseID": 1000157, + "DigitalItems": [ + { + "ISBN": "9781256396362", + "ItemInternalID": 2000268, + "OptsOutStatus": false, + "Paid": false, + "Price": 24.26 + } + ] + } + ], + "BalanceToPay": 219.85, + "PaymentDeadline": "2013-04-19", + "BookstoreDigitalMaterialLink": "https://www.ubookstore.com/materials?idset=500555,3000366,2000268", + "BookstoreCheckOutLink": "https://www.ubookstore.com/app/site/query/additemtocart.nl?n=3&buyid=multi&multi=500555,1;3000366,1;2000268,1;", + "LastUpdated": "2013-2-09T00:51:09.769370-08:00" + }, + { + "Year": 2013, + "Quarter": "summer", + "IACourses": [ + { + "SLN": 13833, + "BookstoreCourseID": 1000111, + "DigitalItems": [ + { + "ISBN": "9781256396062", + "ItemInternalID": 500555, + "OptsOutStatus": false, + "Paid": true, + "Price": 99.00 + } + ] + } + ], + "BalanceToPay": 99.00, + "PaymentDeadline": "2013-07-12", + "BookstoreDigitalMaterialLink": "https://www.ubookstore.com/materials?idset=500555", + "BookstoreCheckOutLink": "https://www.ubookstore.com/app/site/query/additemtocart.nl?n=3&buyid=multi&multi=500555,1;", + "LastUpdated": "2013-2-09T00:51:09.769370-08:00" + } +] diff --git a/myuw/templates/restclients/customform/book/iacourse.html b/myuw/templates/restclients/customform/book/iacourse.html new file mode 100644 index 0000000000..8bd96f168b --- /dev/null +++ b/myuw/templates/restclients/customform/book/iacourse.html @@ -0,0 +1,11 @@ +

University Bookstore Inclusive Access Course Materials

+{% include "restclients/customform/_common/regid.html" %} + +

Regids:

+ diff --git a/myuw/templates/supporttools/custom_sidebar_links.html b/myuw/templates/supporttools/custom_sidebar_links.html index ae3ca84075..c63ec7fd77 100644 --- a/myuw/templates/supporttools/custom_sidebar_links.html +++ b/myuw/templates/supporttools/custom_sidebar_links.html @@ -141,6 +141,13 @@

Web Services

{% endif %} + {% url 'myuw_rest_search' 'book' 'iacourse.html' as iacourse_url %} + {% if iacourse_url %} +
  • + IA Courses +
  • + {% endif %} + {% url 'myuw_rest_search' 'upass' 'index.html' as upass_url %} {% if upass_url %}
  • diff --git a/myuw/test/api/test_academic_calendar.py b/myuw/test/api/test_academic_calendar.py index 63300cb87e..b9601afd39 100644 --- a/myuw/test/api/test_academic_calendar.py +++ b/myuw/test/api/test_academic_calendar.py @@ -55,7 +55,7 @@ def test_muwm_2489(self): # test MUWM_4485, follow_link = urlopen(data[0]["event_url"]) - self.assertEquals(follow_link.reason, "OK") + self.assertEquals(follow_link.reason, "") def test_current_events(self): self.set_user('javerage') diff --git a/myuw/test/api/test_textbook.py b/myuw/test/api/test_textbook.py index 4d3095b106..d51eb9f0d9 100644 --- a/myuw/test/api/test_textbook.py +++ b/myuw/test/api/test_textbook.py @@ -2,9 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 import json +from myuw.views.api.textbook import get_payment_quarter +from myuw.test import get_request_with_date from myuw.test.api import MyuwApiTest, require_url, fdao_bookstore_override -from myuw.test import get_request_with_user, get_request_with_date - VERBACOMPARE_URL_PREFIX = 'http://uw-seattle.verbacompare.com' IMAGE_URL_PREFIX = 'www7.bookstore.washington.edu/MyUWImage.taf' @@ -14,7 +14,7 @@ class TestApiBooks(MyuwApiTest): '''Tests textbooks api''' - @require_url('myuw_home') + @require_url('myuw_book_api') def test_javerage_books(self): self.set_user('javerage') response = self.get_response_by_reverse( @@ -53,3 +53,64 @@ def test_no_sche_books(self): 'quarter': 'spring', 'summer_term': ''}) self.assertEquals(response.status_code, 200) + + @require_url('myuw_iacourse_digital_material_api') + def test_digital_material_api(self): + self.set_user('javerage') + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material') + self.assertEquals(response.status_code, 200) + data = json.loads(response.content) + self.assertEquals(data["quarter"], "spring") + self.assertEquals(data["year"], 2013) + self.assertEquals(data["balance"], 219.85) + + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material_api', + kwargs={'year': 2013, + 'quarter': 'spring'}) + self.assertEquals(response.status_code, 200) + data = json.loads(response.content) + self.assertEquals(data["quarter"], "spring") + self.assertEquals(data["year"], 2013) + self.assertEquals(data["balance"], 219.85) + + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material_api', + kwargs={'year': 2013, + 'quarter': 'autumn'}) + self.assertEquals(response.status_code, 404) + + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material_api', + kwargs={'year': 2013, + 'quarter': 'winter'}) + self.assertEquals(response.status_code, 404) + + self.set_user('jbothell') + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material_api', + kwargs={'year': 2013, + 'quarter': 'spring'}) + self.assertEquals(response.status_code, 404) + + response = self.get_response_by_reverse( + 'myuw_iacourse_digital_material') + self.assertEquals(response.status_code, 404) + + def test_get_payment_quarter(self): + request = get_request_with_date('2013-06-18') + q = get_payment_quarter(request) + self.assertEquals(q.quarter, "spring") + + request = get_request_with_date('2013-06-19') + q = get_payment_quarter(request) + self.assertEquals(q.quarter, "summer") + + request = get_request_with_date('2013-09-19') + q = get_payment_quarter(request) + self.assertEquals(q.quarter, "autumn") + + request = get_request_with_date('2013-12-27') + q = get_payment_quarter(request) + self.assertEquals(q.quarter, "winter") diff --git a/myuw/test/dao/test_textbook.py b/myuw/test/dao/test_textbook.py index 83a4e09705..7350077592 100644 --- a/myuw/test/dao/test_textbook.py +++ b/myuw/test/dao/test_textbook.py @@ -6,7 +6,8 @@ from uw_sws.models import Term from restclients_core.exceptions import DataFailureException from myuw.dao.textbook import ( - get_textbook_by_schedule, get_order_url_by_schedule) + get_textbook_by_schedule, get_order_url_by_schedule, + get_iacourse_status) from myuw.dao.instructor_schedule import get_instructor_schedule_by_term from myuw.dao.term import get_current_quarter from myuw.test import get_request_with_user, get_request_with_date @@ -58,3 +59,22 @@ def test_get_uwt_inst_textbook(self): self.assertEqual(len(schedule.sections), 3) books = get_textbook_by_schedule(schedule) self.assertEquals(len(books), 0) + + def test_get_iacourse_status(self): + req = get_request_with_user( + 'javerage', get_request_with_date("2013-05-01")) + term = get_current_quarter(req) + data = get_iacourse_status(req, term) + self.assertIsNotNone(data.json_data()) + + req = get_request_with_user( + 'javerage', get_request_with_date("2013-06-25")) + term = get_current_quarter(req) + data = get_iacourse_status(req, term) + self.assertIsNotNone(data.json_data()) + + req = get_request_with_user( + 'javerage', get_request_with_date("2013-12-31")) + term = get_current_quarter(req) + data = get_iacourse_status(req, term) + self.assertIsNone(data) diff --git a/myuw/urls.py b/myuw/urls.py index 92b4d7f8fe..0c3315a1cf 100644 --- a/myuw/urls.py +++ b/myuw/urls.py @@ -36,13 +36,13 @@ LTIInstSectionDetails) from myuw.views.api.instructor_schedule import (InstScheCurQuar, InstScheQuar, InstSect) -from myuw.views.api.instructor_section_display import \ - CloseMinicard, PinMinicard +from myuw.views.api.instructor_section_display import ( + CloseMinicard, PinMinicard) from myuw.views.api.finance import Finance from myuw.views.api.hfs import HfsBalances from myuw.views.api.future_schedule import StudClasScheFutureQuar -from myuw.views.api.prev_unfinished_schedule import \ - StudUnfinishedPrevQuarClasSche +from myuw.views.api.prev_unfinished_schedule import ( + StudUnfinishedPrevQuarClasSche) from myuw.views.api.grad import MyGrad from myuw.views.api.iasystem import IASystem from myuw.views.api.library import MyLibInfo @@ -50,7 +50,8 @@ from myuw.views.api.profile import MyProfile from myuw.views.api.category_links import CategoryLinks from myuw.views.api.other_quarters import RegisteredFutureQuarters -from myuw.views.api.textbook import Textbook, TextbookCur +from myuw.views.api.textbook import ( + Textbook, TextbookCur, IACDigitalItemsCur, IACDigitalItems) from myuw.views.api.notices import Notices from myuw.views.api.myplan import MyPlan from myuw.views.api.academic_events import AcademicEvents @@ -62,8 +63,8 @@ from myuw.views.api.directory import MyDirectoryInfo from myuw.views.lti.photo_list import LTIPhotoList from myuw.views.api.visual_schedule import VisSchedCurQtr, VisSchedOthrQtr -from myuw.views.api.hx_toolkit import HxToolkitMessage, HxToolkitWeekMessage, \ - HxToolkitMessageList +from myuw.views.api.hx_toolkit import ( + HxToolkitMessage, HxToolkitWeekMessage, HxToolkitMessageList) from myuw.views.api.resources import (ResourcesList, ResourcesPin, PinnedResources) @@ -146,6 +147,12 @@ r'(?P[-,fulabterm]*)$', Textbook.as_view(), name="myuw_book_api"), + re_path(r'^api/v1/iacourse/(?P\d{4}),(?P[a-z]+)', + IACDigitalItems.as_view(), + name="myuw_iacourse_digital_material_api"), + re_path(r'^api/v1/iacourse/current/?$', + IACDigitalItemsCur.as_view(), + name="myuw_iacourse_digital_material"), re_path(r'^api/v1/categorylinks/(?P.*?)$', CategoryLinks.as_view(), name="myuw_links_api"), diff --git a/myuw/util/cache.py b/myuw/util/cache.py index 8b08e2de44..25a22ff490 100644 --- a/myuw/util/cache.py +++ b/myuw/util/cache.py @@ -17,6 +17,7 @@ class MyUWMemcachedCache(RestclientPymemcacheClient): def get_cache_expiration_time(self, service, url, status=None): if "myplan_auth" == service: return FIFTEEN_MINS * 3 + if "myplan" == service: return FIVE_SECONDS diff --git a/myuw/views/api/textbook.py b/myuw/views/api/textbook.py index 2c5fe31b0a..bd623c1734 100644 --- a/myuw/views/api/textbook.py +++ b/myuw/views/api/textbook.py @@ -1,17 +1,19 @@ # Copyright 2023 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 +from datetime import timedelta import logging import traceback from restclients_core.exceptions import DataFailureException from myuw.dao.registration import get_schedule_by_term from myuw.dao.instructor_schedule import get_instructor_schedule_by_term -from myuw.dao.term import get_specific_term, get_current_quarter +from myuw.dao.term import ( + get_specific_term, get_comparison_date, get_current_quarter, get_term_after) from myuw.dao.textbook import ( - get_textbook_by_schedule, get_order_url_by_schedule) + get_textbook_by_schedule, get_order_url_by_schedule, + get_iacourse_status) from myuw.logger.timer import Timer -from myuw.logger.logresp import ( - log_api_call, log_msg, log_data_not_found_response) +from myuw.logger.logresp import log_api_call from myuw.views import prefetch_resources from myuw.views.api import ProtectedAPI from myuw.views.error import handle_exception, data_not_found @@ -104,3 +106,58 @@ def get(self, request, *args, **kwargs): timer, request, get_current_quarter(request), None) except Exception: return handle_exception(logger, timer, traceback) + + +class IACDigitalItems(ProtectedAPI): + # MUWM-5272 + + def get(self, request, *args, **kwargs): + """ + myuw_iacourse_digital_material_api + GET returns 200 with textbooks for the given quarter + """ + timer = Timer() + year = kwargs.get("year") + quarter = kwargs.get("quarter") + try: + ret_obj = get_iacourse_status( + request, get_specific_term(year, quarter)) + if ret_obj is None: + return data_not_found() + return self.json_response(ret_obj.json_data()) + except Exception: + return handle_exception(logger, timer, traceback) + finally: + log_api_call( + timer, request, "IACourse_Status {}.{}".format( + year, quarter)) + + +class IACDigitalItemsCur(ProtectedAPI): + # MUWM-5272 + + def get(self, request, *args, **kwargs): + """ + myuw_iacourse_digital_material + GET returns 200 with textbooks for the given quarter + """ + timer = Timer() + try: + ret_obj = get_iacourse_status( + request, get_payment_quarter(request)) + if ret_obj is None: + return data_not_found() + return self.json_response(ret_obj.json_data()) + except Exception: + return handle_exception(logger, timer, traceback) + finally: + log_api_call(timer, request, "IACDigitalItemsCur") + + +def get_payment_quarter(request): + term = get_current_quarter(request) + term_after = get_term_after(term) + comparison_date = get_comparison_date(request) + if comparison_date > term_after.first_day_quarter - timedelta(days=6): + return term_after + return term diff --git a/myuw/views/rest_search.py b/myuw/views/rest_search.py index a21617a656..3dfe380c0d 100644 --- a/myuw/views/rest_search.py +++ b/myuw/views/rest_search.py @@ -23,11 +23,15 @@ def get_proxy_url(self, request, service, url): service, url, request.POST)) if service == "book": - url = "uw/json_utf8_202007.ubs" - url = "{}?quarter={}&sln1={}&returnlink=t".format( - "uw/json_utf8_202007.ubs", - request.POST["quarter"], - request.POST["sln1"]) + if "iacourse" == url: + url = "uw/iacourse_status.json?regid={}".format( + request.POST["uwregid"]) + else: + url = "uw/json_utf8_202007.ubs" + url = "{}?quarter={}&sln1={}&returnlink=t".format( + "uw/json_utf8_202007.ubs", + request.POST["quarter"], + request.POST["sln1"]) elif service == "grad": params = self.format_params(request) elif service == "hfs": diff --git a/myuw_vue/accounts.js b/myuw_vue/accounts.js index 5e6b88e17f..6b6e566408 100644 --- a/myuw_vue/accounts.js +++ b/myuw_vue/accounts.js @@ -22,6 +22,7 @@ import profile from './vuex/store/profile'; import upass from './vuex/store/upass'; import tuition from './vuex/store/tuition'; import notices from './vuex/store/notices'; +import iac from './vuex/store/iacourse-digital-material'; vueConf.store.registerModule('hfs', hfs); vueConf.store.registerModule('library', library); @@ -29,6 +30,7 @@ vueConf.store.registerModule('profile', profile); vueConf.store.registerModule('upass', upass); vueConf.store.registerModule('tuition', tuition); vueConf.store.registerModule('notices', notices); +vueConf.store.registerModule('iac', iac); vueConf.store.commit('addVarToState', { name: 'page', diff --git a/myuw_vue/components/accounts/tuition-fees.vue b/myuw_vue/components/accounts/tuition-fees.vue index e9c0de6c4a..9cbb78842c 100644 --- a/myuw_vue/components/accounts/tuition-fees.vue +++ b/myuw_vue/components/accounts/tuition-fees.vue @@ -19,7 +19,7 @@
  • - + @@ -90,9 +84,7 @@ - Make payment - + >Make payment @@ -107,7 +99,8 @@
    PCE-Continuum College
    - Account Statement @@ -130,14 +123,64 @@
  • + +
  • +

    UW Day One Access Fees

    + + + + + + + + +
  • + +
  • + + + + + +
  • - Give access to your tuition account and financial aid information - + >Give access to your tuition account and financial aid information to parents or other third parties.

    @@ -163,13 +206,13 @@ @@ -180,9 +223,7 @@ - + diff --git a/myuw_vue/components/textbooks/book.vue b/myuw_vue/components/textbooks/book.vue index b172a0b7e3..fc77f0ba51 100644 --- a/myuw_vue/components/textbooks/book.vue +++ b/myuw_vue/components/textbooks/book.vue @@ -11,9 +11,7 @@

    +
    + DIGITAL MATERIAL +
    +
    + Paid + Opted out + Payment due +
    {{ book.authors > 1 ? "Authors" : "Author" }}
    @@ -36,10 +42,16 @@
    Price
    - ${{ book.lowest_price.toFixed(2) }} - to - ${{ book.highest_price.toFixed(2) }} + + ${{ book.lowest_price.toFixed(2) }} + to + ${{ book.highest_price.toFixed(2) }} + + + ${{ book.lowest_price.toFixed(2) }} +
    +
    Digital: ${{ digitalItem.price.toFixed(2) }}
    Visit @@ -67,14 +79,57 @@ diff --git a/myuw_vue/tests/mock_data/textbooks/javerage-iac-2013-spr.json b/myuw_vue/tests/mock_data/textbooks/javerage-iac-2013-spr.json new file mode 100644 index 0000000000..6a17857cdb --- /dev/null +++ b/myuw_vue/tests/mock_data/textbooks/javerage-iac-2013-spr.json @@ -0,0 +1,50 @@ +{ + "quarter": "spring", + "year": 2013, + "balance": 219.85, + "payment_due_day": "2013-04-19", + "bookstore_digital_material_url": "https://www.ubookstore.com/materials?idset=500555,3000366,2000268", + "bookstore_checkout_url": "https://www.ubookstore.com/app/site/query/additemtocart.nl?n=3&buyid=multi&multi=500555,1;3000366,1;2000268,1;", + "last_updated": "2013-02-09T00:51:09.769370-08:00", + "ia_courses": { + "13833": { + "sln": 13833, + "bookstore_course_id": 1000111, + "digital_items": { + "9781256396362": { + "isbn": "9781256396362", + "bookstore_item_id": 500555, + "opt_out_status": false, + "paid": true, + "price": 99.0 + } + } + }, + "18532": { + "sln": 18532, + "bookstore_course_id": 1000133, + "digital_items": { + "9780878935970": { + "isbn": "9780878935970", + "bookstore_item_id": 3000366, + "opt_out_status": true, + "paid": false, + "price": 96.59 + } + } + }, + "13830": { + "sln": 13830, + "bookstore_course_id": 1000157, + "digital_items": { + "9781256396362": { + "isbn": "9781256396362", + "bookstore_item_id": 2000268, + "opt_out_status": false, + "paid": false, + "price": 24.26 + } + } + } + } +} diff --git a/myuw_vue/tests/textbooks.test.js b/myuw_vue/tests/textbooks.test.js index 5bc7eef608..ae4afc1e7d 100644 --- a/myuw_vue/tests/textbooks.test.js +++ b/myuw_vue/tests/textbooks.test.js @@ -12,7 +12,9 @@ import LinkButton from '../components/_templates/link-button.vue'; import stud_schedule from '../vuex/store/schedule/student'; import inst_schedule from '../vuex/store/schedule/instructor'; import textbooks from '../vuex/store/textbooks'; +import iac from '../vuex/store/iacourse-digital-material'; +import javgBook from './mock_data//textbooks/javerage-iac-2013-spr.json'; import mockStudCourses from './mock_data/stud_schedule/javerage2013Spring.json'; import mockStudTextbook from './mock_data/textbooks/javerage-2013-spr.json'; @@ -41,11 +43,14 @@ describe('Textbook cards', () => { stud_schedule, inst_schedule, textbooks, + iac, }, state: { user: { affiliations: { seattle: true, + student: true, + bothell: true, } } } @@ -58,11 +63,12 @@ describe('Textbook cards', () => { '/api/v1/book/2013,spring': mockStudTextbook, '/api/v1/schedule/2013,spring': mockStudCourses, '/api/v1/instructor_schedule/2013,spring': mockInstSche, + '/api/v1/iacourse/2013,spring': javgBook, }; return Promise.resolve({data: urlData[url]}); }); - const wrapper = mount(Textbooks, {store, localVue, + let wrapper = mount(Textbooks, {store, localVue, propsData: {'term': '2013,spring'}}); await new Promise(setImmediate); expect(wrapper.vm.term).toEqual('2013,spring'); @@ -81,6 +87,13 @@ describe('Textbook cards', () => { expect(bookData.enrolledSections.length).toBe(5); expect(bookData.sections.length).toBe(11); expect(bookData.hasTeachingSections).toBe(true); + + const section5 = wrapper.vm.bookData.enrolledSections[4]; + wrapper = mount(Book, { + store, localVue, + propsData: { 'book': section5.books[0], 'sln': section5.sln } + }); + expect(wrapper.vm.digitalItem).toBeTruthy(); }); it('Verify other campus courses', async () => { @@ -91,6 +104,7 @@ describe('Textbook cards', () => { const urlData = { '/api/v1/book/2013,spring': mockTextbook, '/api/v1/schedule/2013,spring': mockStudCourses, + '/api/v1/iacourse/2013,spring': javgBook, }; return Promise.resolve({data: urlData[url]}); }); diff --git a/myuw_vue/tests/tuition.test.js b/myuw_vue/tests/tuition.test.js index b3ce5e1da6..7c2c27bbe4 100644 --- a/myuw_vue/tests/tuition.test.js +++ b/myuw_vue/tests/tuition.test.js @@ -6,12 +6,15 @@ import Vuex from 'vuex'; import {createLocalVue} from './helper'; import tuition from '../vuex/store/tuition'; import notices from '../vuex/store/notices'; +import iac from '../vuex/store/iacourse-digital-material'; + import TuitionFees from '../components/accounts/tuition-fees.vue'; import TuitionRes from '../components/accounts/tuition-resources.vue'; import CardStatus from '../components/_templates/card-status.vue'; import LinkButton from '../components/_templates/link-button.vue'; import FinAid from '../components/_common/finaid.vue'; +import javgBook from './mock_data//textbooks/javerage-iac-2013-spr.json'; import jbotTuition from './mock_data/tuition/jbothell.json'; import jbotNotices from './mock_data/notice/jbothell.json'; import javgTuition from './mock_data/tuition/javerage.json'; @@ -29,6 +32,7 @@ describe('Tuition store', () => { modules: { tuition, notices, + iac, }, state: { cardDisplayDates: { @@ -40,6 +44,8 @@ describe('Tuition store', () => { pce: false, grad_c2: false, undergrad_c2: false, + bothell: true, + seattle: true, } } } @@ -48,6 +54,9 @@ describe('Tuition store', () => { it('Evaluate the computed properties of jbothell', async () => { axios.get.mockImplementation((url) => { + if (url === '/api/v1/iacourse/current') { + return Promise.reject({ response: { status: 404 } }); + } const urlData = { '/api/v1/notices/': jbotNotices, '/api/v1/finance/': jbotTuition, @@ -59,6 +68,9 @@ describe('Tuition store', () => { expect(wrapper.vm.isStudent).toBe(true); expect(wrapper.vm.isC2Grad).toBe(false); + expect(wrapper.vm.seaStud).toBe(true); + expect(wrapper.vm.botStud).toBe(true); + expect(wrapper.vm.hasIacData).toBe(undefined); expect(wrapper.vm.isC2).toBe(false); expect(wrapper.vm.isPCE).toBe(false); expect(wrapper.vm.notices.length).toBe(14); @@ -84,6 +96,7 @@ describe('Tuition store', () => { const urlData = { '/api/v1/notices/': javgNotices, '/api/v1/finance/': javgTuition, + '/api/v1/iacourse/current': javgBook, }; return Promise.resolve({data: urlData[url]}); }); @@ -104,11 +117,16 @@ describe('Tuition store', () => { expect(wrapper.findComponent(LinkButton).exists()).toBe(true); expect(wrapper.findComponent(FinAid).exists()).toBe(true); expect(wrapper.findComponent(TuitionRes).exists()).toBe(true); - expect(wrapper.findAllComponents(CardStatus).length).toBe(3); + expect(wrapper.findAllComponents(CardStatus).length).toBe(5); + expect(wrapper.vm.hasIacData).toBeTruthy; + expect(wrapper.vm.dayOneAccessDueDateFromNow).toBeTruthy; }); it('Evaluate the computed properties of jpce', async () => { axios.get.mockImplementation((url) => { + if (url === '/api/v1/iacourse/current') { + return Promise.reject({ response: { status: 404 } }); + } const urlData = { '/api/v1/notices/': gc2Notices, '/api/v1/finance/': gc2Tuition, @@ -120,5 +138,6 @@ describe('Tuition store', () => { expect(wrapper.vm.hasTuitionDate).toBe(true); expect(wrapper.vm.pceBalance).toBe(2897.00); expect(wrapper.vm.tuiBalance).toBe(-10.00); + expect(wrapper.vm.hasIacData).toBe(undefined); }); }); diff --git a/myuw_vue/textbooks.js b/myuw_vue/textbooks.js index c75f4e4114..d1c943fd3b 100644 --- a/myuw_vue/textbooks.js +++ b/myuw_vue/textbooks.js @@ -9,10 +9,12 @@ import Textbooks from './components/textbooks/textbooks.vue'; import instSchedule from './vuex/store/schedule/instructor'; import studSchedule from './vuex/store/schedule/student'; import textbooks from './vuex/store/textbooks'; +import iac from './vuex/store/iacourse-digital-material'; vueConf.store.registerModule('inst_schedule', instSchedule); vueConf.store.registerModule('stud_schedule', studSchedule); vueConf.store.registerModule('textbooks', textbooks); +vueConf.store.registerModule('iac', iac); vueConf.store.commit('addVarToState', { name: 'page', diff --git a/myuw_vue/vuex/store/iacourse-digital-material.js b/myuw_vue/vuex/store/iacourse-digital-material.js new file mode 100644 index 0000000000..4501d317a3 --- /dev/null +++ b/myuw_vue/vuex/store/iacourse-digital-material.js @@ -0,0 +1,10 @@ +import { fetchBuilder, extractData, buildWith } from './model_builder'; + + +const customActions = { + fetch: fetchBuilder('/api/v1/iacourse/', extractData, 'json'), +}; + +export default buildWith( + { customActions } +); diff --git a/setup.py b/setup.py index a97c4b754b..990cb074b0 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 'UW-Django-SAML2~=1.7', 'aws-message-client~=1.5', 'UW-RestClients-Core~=1.4', - 'UW-RestClients-Bookstore~=1.2', + 'UW-RestClients-Bookstore~=1.3', 'UW-RestClients-Canvas~=1.2', 'UW-RestClients-CoDa~=1.0', 'UW-RestClients-Grad~=1.1', @@ -59,7 +59,7 @@ 'Django-Persistent-Message~=1.3', 'Django-Safe-EmailBackend~=1.2', 'django_client_logger<3.0', - 'UW-HX-Toolkit~=2.7.8', + 'UW-HX-Toolkit~=2.7', 'django-blti~=2.2', 'nh3', 'pyyaml',