The purpose of this assignment is to help you learn or review client-side web programming by creating a three-tiered single page application (SPA).
Make sure you study the COS 333 Policies web page before doing this assignment or any of the course's assignments.
This assignment asks you to compose a web application. On the server side your application must use the Python Flask framework. On the client side your application must use HTML and Javascript with AJAX, and may use a client-side library. On the client side your application must use CSS, but may do so indirectly by using Bootstrap.
python runserver.py someport
. Your grader must not be required to perform a build of your application before running it.
Arguably, Assignment 3 is flawed. The flaw is not in your implementation; instead the flaw (intentionally) is in the specification.
The problem is that an application that conforms to the Assignment 3 specification often has inconsistent page states. For example consider this sequence:
At that point the page's Dept input element contains COS, but the page displays data for all classes, not just for those classes whose department is COS. In that sense the application's page state is inconsistent.
Generalizing, any web application that accepts input and displays the resulting output in the same page runs the risk of inconsistent page states. One solution is to redesign the application such that it generates its output in a distinct page. Another solution is to redesign the application such that it refreshes its output within its single page with each input keystroke, such that the page state is always consistent. This assignment requires you to use the latter solution.
As with previous assignments, pretend that you're working for Princeton's Registrar's Office. You're given a database containing data about classes and courses offered during an upcoming Princeton semester. Your task is to create an application that allows Princeton students and other interested parties to query the database.
More specifically, your task is to redesign your Assignment 3 solution such that your application uses JavaScript and AJAX to generate new output in its single page dynamically as the user types. Your task also is to make your application responsive to the browser window size (such that it is a mobile web application), and better aesthetically.
Browse to the TigerFile page for this assignment. Download the reg4.zip
file. Then unzip that file to create files named reg.sqlite
, testregoverviewsgiven.py
, and testregdetailsgiven.py
. Subsequent sections of this document describe those files.
The database is identical to the one from Assignment 1. The specification of Assignment 1 provides a description. The database is stored in the file named reg.sqlite
.
A reference application is running at https://cos333reg2.cs.princeton.edu. You can test your application by comparing its behavior with that of the reference application.
Compose a program named runserver.py
. When executed with -h as a command-line argument, runserver.py
must display a help message that describes the program's behavior:
$ python runserver.py -h usage: runserver.py [-h] port The registrar application positional arguments: port the port at which the server should listen options: -h, --help show this help message and exit
Your runserver.py
program must run the Flask test server on the specified port, which in turn must run your application.
Your application's page must contain a header and a footer, both of which are "sticky" with respect to scrolling of the page.
Your application's page must contain four input
elements. The input
elements must allow the user to specify a dept
, coursenum
, area
, and/or title
. In contrast to the Assignment 3 application, your application's primary page must not contain a submit button.
Your application must handle the '%' and '_' characters as ordinary (not wildcard) characters, just as described in the previous assignment specifications.
In addition to the four input
elements, your page must contain a results area, that is, a table containing the data that match the criteria defined by the values of the four elements. Specifically, the table must contain the classid
, dept
, coursenum
, area
, and title
of each class that matches the specified criteria, or of all classes if the user specifies no criteria. The rows must be sorted; the primary sort must be by dept
in ascending order, the secondary sort must be by coursenum
in ascending order, and tertiary sort must be by classid
in ascending order. The table must be well formatted.
When the user types a character in the dept
, coursenum
, area
, or title
input element, the page must use JavaScript and AJAX to refresh the contents of the results area of the page. For correctness, your JavaScript code must cancel any existing AJAX communication immediately before starting a new one, as described in lectures. For efficiency, your JavaScript code must "debounce" the AJAX communication at the 0.5 second level, as also described in lectures.
Your page must display a button for each class. When the user clicks on the button for a particular class, your page must use JavaScript and AJAX to display the details for that class in a modal. The modal must display the class's classid
, days
, starttime
, endtime
, bldg
, roomnum
, courseid
, dept(s)
, coursenum(s)
, area
, title
, descrip
, prereqs
, and profname(s)
. The data must be well formatted.
Your page and modal must be reasonably attractive. Moreover, your page must be responsive to the size of the browser window. Specifically, your page must align the four input elements horizontally in wide windows and vertically in narrow windows. In that sense your application must be a mobile web application.
We assume that the layouts of your application's page and modal will be the same as those of the reference application. If you have a principled reason why you would like your application's layouts to differ from those of the reference application, then please discuss the matter with the course's instructors before you commit to your design. No surprises please!
To request the application's page, a client (typically a browser) must send a HTTP request generated from a URL having any of these forms:
http://somehost:someport http://somehost:someport/ http://somehost:someport/index
Your application's response must be a page consisting of HTML and JavaScript code. As noted previously, your JavaScript code must use one of these three options:(1) no client-side library, (2) jQuery, or (3) React.
To request class overviews, a client (not necessarily a browser) must send a HTTP request generated from a URL of this form:
http://somehost:someport/regoverviews?dept=somedept&coursenum=somecoursenum&area=somearea&title=sometitle
In that URL somedept, somecoursenum, somearea, or sometitle may be blank. For example, a client might send a HTTP request generated from this URL:
http://somehost:someport/regoverviews?dept=COS&coursenum=2&area=qr&title=intro
Your application's HTTP response must contain a JSON document which has the same format as prescribed in Assignment 2. For example, upon receipt of the example request shown previously, your application must send a HTTP response containing this JSON document:
[true, [ {"classid":8308, "dept":"COS", "coursenum":"217", "area":"qr", "title":"Introduction to C_Science Programming Systems"}, {"classid":9240, "dept":"COS", "coursenum":"342", "area":"qr", "title":"Introduction to Graph Theory"}]]
If your application, for whatever reason, cannot fulfull the class overviews request, then it must send a HTTP response containing this JSON document:
[false, "someerrormessage"]
The Error Handling section of this document provides more specifics.
To request class details, a client (not necessarily a browser) must send a HTTP request generated from a URL of this form:
http://somehost:someport/regdetails?classid=someclassid
For example, a client might send a HTTP request generated from this URL:
http://somehost:someport/regdetails?classid=8321
Your application's HTTP response must contain a JSON document which has the same format as prescribed in Assignment 2. For example, upon receipt of the example request shown previously, your application must send a HTTP response containing this JSON document:
[true, { "classid":8321, "days":"TTh", "starttime":"11:00AM", "endtime":"12:20PM", "bldg":"FRIEN", "roomnum":"006", "courseid":3672, "deptcoursenums":[{"dept": "COS", "coursenum": "333"}], "area":"", "title":"Advanced C%Science Programming Techniques", "descrip":"This is a course about the practice of programming. Programming is more than just writing code. Programmers must also assess tradeoffs, choose among design alternatives, debug and test, improve performance, and maintain software written by themselves & others. At the same time, they must be concerned with compatibility, robustness, and reliability, while meeting specifications. Students will have the opportunity to develop these skills by working on their own code and in group projects.", "prereqs":"COS 217 and COS 226.", "profnames": ["Brian W. Kernighan"] }]
Within the deptcoursenums
list the elements must be sorted primarily by department alphabetically and secondarily by course number alphabetically. Within the profnames
list the elements must be sorted alphabetically.
[false, "somerrormessage"]
The Error Handling section of this document provides more specifics.
Your application must be robust. It must be impossible for any client request to cause your application to exit. Your application must handle the following errors:
Generally it won't be possible to automate the testing of your application's error handling via yourtestregoverviews.py
ortestregdetails.py
programs. Instead you must test your application's error handling manually.
If your runserver.py
is given command-line arguments that argparse can detect as incorrect, then argparse indeed must detect them as incorrect. Then your program must write a descriptive argparse-generated message to its stderr
and exit the process with status 2, as is the default when using argparse.
Testing: Run yourrunserver.py
with incorrect command-line arguments and observe the result.
If a "class overviews" or "class details" request sent by your page fails, maybe because the server isn't running at the time of the call, then the browser must display the message "Error: Failed to fetch data from server" in an alert box and leave your page unchanged.
Testing: Run yourrunserver.py
and browse to it. Kill yourrunserver.py
. In the browser initiate a "class overviews" request and observe observe the result. In the browser initiate a "class details" request and observe the result.
[false, "no class with classid X exists"]
Testing: Run yourrunserver.py
onsomeipaddress
atsomeport
. Browse tohttp://someipaddress:someport/regdetails?classid=99999
. In the browser observe the result, which must be the proper JSON document.
That could happen if the client directly (that is, not through your page) sends a "class details" request. That also could happen if (1) the browser through your page sends a "class overviews" request that displays data for classid 8321 (the classid for COS 333), (2) some other process deletes the row with classid 8321 from the classes table in the database, and (3) the browser through your page sends a "class details" request for classid 8321. In that case the browser must display the "no class with classid X exists" error message in an alert box and leave your page unchanged.
Testing: Run yourrunserver.py
, and browse to it. Make sure the COS 333 class is displayed in the class overviews table. Edit your database, deleting the data for COS 333. Through the browser initiate a "class details" request for COS 333, and observe the result.
If a client sends a "class details" request using a URL that is missing the classid=X name/value pair, or in which X is missing, then your application must send a HTTP response containing this JSON:
[false, "missing classid"]
Testing: Run yourrunserver.py
onsomeipaddress
atsomeport
. Browse tohttp://someipaddress:someport/regdetails
. In the browser observe the result, which must be the proper JSON document. Then browse tohttp://someipaddress:someport/regdetails?classid=
. In the browser observe the result, which must be the proper JSON document.
If a client sends a "class details" request using a URL containing the classid=X name/value pair, and X is not an integer, then your application must send a HTTP response containing this JSON:
[false, "non-integer classid"]
Testing: Run yourrunserver.py
onsomeipaddress
atsomeport
. Browse tohttp://someipaddress:someport/regdetails?classid=abcd
. In the browser observe the result, which must be the proper JSON document.
If your application cannot open the database when it receives a "class overviews" or "class details" request, then your application must write a descriptive error message — the one contained within the thrown exception object — to its stderr
. Then it must send to the client this JSON response:
[false, "A server error occurred. Please contact the system administrator."]
If the request was made by a browser through your page, then the browser must display that message in an alert box and leave the page unchanged.
Testing: Run yourrunserver.py
, and browse to it. Delete your database. Through the browser initiate a "class overviews" request and observe the result. Through the browser initiate a "class details" request and observe the result.
If your application discovers that the database is corrupted when it receives a "class overviews" or "class details" request, then your application must write a descriptive error message — the one contained within the thrown exception object — to its stderr
. Then it must send to the client this JSON response:
[false, "A server error occurred. Please contact the system administrator."]
If the request was made by a browser through your page, then the browser must display that message in an alert box and leave the page unchanged.
Testing: Run yourrunserver.py
, and browse to it. Corrupt your database, say, by dropping a critical table. Through the browser initiate a "class overviews" request and observe the result. Through the browser initiate a "class details" request and observe the result.
We recommend that you develop your assignment solution using three steps:
Each class overview displayed in your page must have an associated button. The HTML for the button for classid must have this form:
<button id="buttonclassid" onclick="getResultsDetails(classid)"> classid </button>
where getResultsDetails
is a function which, given classid
, performs an AJAX request to fetch the details for the specified class, and then displays those class details in a modal. (The id
attribute is required for test automation, as described later in this document.)
The last step — displaying the class details in a modal — is likely to be the trickiest one. Lectures provide hints about how to display a Bootstrap model using either no library, jQuery, or React. With React you will find it easier to use the react-bootstrap library rather than the bootstrap library.
Test your application just as you tested your Assignment 3 application. Also test by accessing your application via a browser on a laptop/desktop computer, with browser windows of various sizes. Then access your application via a browser on a smart phone. Your application have a reasonable appearance on both laptop/desktop computers and mobile devices.
Concerning test automation...
As noted previously, testing of the error-handling aspects of your application must be manual. But testing of the non-error-handling aspects must be automated.
Automation of the testing of your Assignment 4 application is mostly the same as was automation of the testing of your Assignment 3 application. The programs testregoverviewsgiven.py
and testregdetailsgiven.py
show how to automate your testing.
Make a copy of testregoverviewsgiven.py
; name the copy testregoverviews.py
. Also make a copy of testregdetailsgiven.py
; name the copy testregdetails.py
. Study the testregoverviews.py
and testregdetails.py
programs. Note that for testregoverviews.py
and testregdetails.py
to work with your application, your code must conform to some constraints. Specifically, your page must contain:
input
element that has an id
attribute whose value is deptInput
.input
element that has an id
attribute whose value is coursenumInput
.input
element that has an id
attribute whose value is areaInput
.input
element that has an id
attribute whose value is titleInput
.table
element that has an id
attribute whose value is overviewsTable
.id
attribute whose value is buttonX
. For example, the button element for the class whose classid is 8321 must have the id
attribute id="button8321"
.Your modal must contain:
table
element that has an id
attribute whose value is classDetailsTable
.table
element that has an id
attribute whose value is courseDetailsTable
.Issue these commands to make sure you know how to run the test programs:
python testregoverviews.py -h python testregdetails.py -h
Note that each program requires you to provide these command-line arguments:
serverURL
, indicating the URL of the application that you want to access.browser
which must be either firefox
or chrome
indicating which browser you want the program to use. Usually chrome is faster than firefox.mode
which must be either normal
or headless
. Running the program in headless mode suppresses the browser window display, and so is faster — but maybe less informative.delay
, typically an integer between 1 and 5, that indicates how long the program should sleep to make sure that AJAX calls have completed.The delay
command-line argument is new, and requires some explanation. Consider the Assignment 3 application. When the test programs enter data into the four input elements and click the Submit button, they can count on the results table appearing in a new page. In contrast, consider the Assignment 4 application. When the test programs enter data into the four input elements, the overviews table does not appear in a new page. Instead the overviews table appears in the same page after some delay, that is, after an AJAX call finishes. So the test programs must "sleep" until the AJAX call finishes.
The amount of time required for AJAX calls to finish isn't constant. If your computer is slow and loaded heavily, then it might take a few seconds; if your computer is fast and loaded lightly, then it might take a small fraction of a second.
That fact motivates the delay
command-line argument. The value of delay
should be some integer, typically between 1 and 5. Thereby delay
tells the test programs how many seconds they should sleep to ensure that AJAX calls finish. If the number is too small, then the test programs will (attempt to) write output before AJAX calls finish. If the number is too big, then the test programs will write proper output, but will take lots of time to complete. Suggestion: Start with 1, and increase as necessary.
Then issue commands such as these to automate testing of your application with respect to its handling of class overviews requests:
python runserver.py 55555 python testregoverviews.py http://somehost:someport chrome normal 2 > out1 2>&1 python testregoverviews.py https://cos333reg2.cs.princeton.edu chrome normal 2 > out2 2>&1
In those commands you can replace chrome
with firefox
, normal
with headless
, and 2
with any reasonable delay.
Then compare the contents of files out1
and out2
. Make sure that the contents of out1
and out2
are identical.
Thereafter, edit your testregoverviews.py
to add additional tests, and repeat that command sequence
Similarly, issue commands such as these to automate testing of your application with respect to its generation of class details requests:
python runserver.py 55555 python testregdetails.py http://somehost:someport chrome normal 2 > out3 2>&1 python testregdetails.py https://cos333reg2.cs.princeton.edu chrome normal 2 > out4 2>&1
In those commands you can replace chrome
with firefox
, normal
with headless
, and 2
with any reasonable delay.
Then compare the contents of files out3
and out4
. Make sure that the contents of out3
and out4
are identical.
Thereafter, edit your testregdetails.py
to add additional tests, and repeat that command sequence.
testregoverviews.py
and testregdetails.py
programs are primarily for you — to help you know if your application is correct, and thereby to help you to earn a high grade. But those programs are secondarily for your grader — to help your grader to grade your work accurately and quickly. So it's important that your application work with those programs. If your application doesn't work with those programs, then your grader will need to test entirely manually. In that unfortunate circumstance, your grade would suffer.
Compose a readme
file. Your readme
file must contain:
Your readme
file must be a plain text file. Don't create your readme
file using Microsoft Word or any other word processor.
Submit your assignment files using the TigerFile page for this assignment. Make sure you submit Assume that your grader already has activated the cos333 virtual environment before he/she runs your programs. The document from the first lecture entitled A COS 333 Computing Environment describes the cos333 virtual environment. Your grade will be based upon: To support your statement testing, you're encouraged (but not required) to use the Python Copyright © 2024 by Robert M. Dondero, Jr.runserver.py
, all files (.py
and .html
) comprising your application, testregoverviews.py
, testregdetails.py
, and readme
.
Grading
pylint
tool, when using the given .pylintrc
file, and when executed via the command python -m pylint *.py
. Ten points (that is, ten percent) of your grade will be based upon the quality of your program style as reported by pylint
. Your grader will start with the 10-point score reported by pylint
. Your grader then will "round down" that score to the 0.5 level to compute your program style grade. For example, if your pylint
score is 9.8, then your program style grade will be 9.5; if your pylint
score is 7.4, then your program style grade will be 7.0.testregoverviews.py
and testregdetails.py
programs. Two points (that is, two percent) of your grade will be based upon your testregoverviews.py
program. You'll receive two points if your testregoverviews.py
program works and is reasonably thorough, one point if your testregoverviews.py
program works and is minimal, and zero points if your testregoverviews.py
program doesn't work or you didn't submit it. Similarly, two points of your grade will be based upon your testregdetails.py
program.
Appendix: Automated Statement Testing
coverage
tool to generate a coverage report showing which lines of your application have and have not been executed by your tests. The steps are the same as they were for Assignment 3.