The purpose of this assignment is to help you learn or review server-side web programming.
Make sure you study the COS 333 Policies web page before doing this assignment or any of the course's assignments.
You may work with one teammate on this assignment, and we prefer that you do so.
It must be the case that either you submit all of your team's files or your teammate submits all of your team's files. (It must not be the case that you submit some of your team's files and your teammate submits some of your team's files.) Your readme
file and your source code files must contain your name and your teammate's name.
This assignment asks you to create a web application. You must do so using the Python Flask framework and Jinja2 template engine.
As with Assignments 1 and 2, assume 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 a web application that allows Princeton students and other interested parties to query the database.
The database is identical to the one from Assignment 1. The specification of Assignment 1 provides a description. The database is stored in a file named reg.sqlite
in the courselab /u/cos333/Asgt3Solution
directory.
A reference application is running at https://cos333reg1.cs.princeton.edu. You can run the reference application by browsing to that address.
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 primary page must contain a form. The form must allow the user to specify a dept
, coursenum
, area
, and/or title
. The form also must contain a button that the user can click to submit the form.
Your program must handle the '%' and '_' characters as ordinary (not wildcard) characters, just as described in the Assignment 1 and 2 specifications.
In addition to the form, your primary page must display a table containing the results of the query that is triggered by submission of the form. 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.
Along with results of the query, your primary page also must display, within its form, the query criteria that user entered to generate those results. For example, if the user entered cos
in the dept
input of the form, entered qr
in the area
input of the form, and submitted the form, then the resulting page must display cos
in the dept
input of the form and qr
in the area
input of the form — along with the data for all classes that match those query criteria.
Your primary page must allow the user to click on the classid
for any of the classes that it displays, thereby allowing the user to learn more about that class. When the user clicks on the classid
for a particular class, your application must display a "secondary" web page containing the selected class's courseid
, days
, starttime
, endtime
, bldg
, roomnum
, dept(s)
, coursenum(s)
, area
, title
, descrip
, prereqs
, and profname(s)
. The data must be presented within two tables.
The secondary page must contain a page link back to the primary page, thus allowing the user easily to perform another class search. In the resulting primary page, the form must show the same data that the user entered most recently. The resulting primary page also must show the class data that resulted from the submission of that form.
Your application must be usable without a pointing device (mouse, touchpad, etc.). That is, your application must be usable with only the keyboard. The user must be able to cycle the keyboard focus through the input elements and links by pressing the Tab key, and trigger a server interaction by pressing the Enter key.
It is possible to design a better interface than the one just described. In fact, you will do so later in the course! We assume that the layout of your application's pages will be the same as that of the reference application. If you have a principled reason why you would like the layout of your application's pages to differ from that of the reference application, then please discuss the matter with the course's instructors before you commit to your design. No surprises please!
Your application must be robust. It must handle the following errors:
If runserver.py
is given an incorrect number of command-line arguments, then it must write a descriptive error message — the one generated by argparse — to its stderr
and exit with status 2.
If runserver.py
is given a non-integer port command-line argument, then it must write a descriptive error message — the one generated by argparse — to its stderr
and exit with status 2.
If the user directly (that is, not through the primary page) commands the browser to fetch a page using a URL that has a classid=XXX
name/value pair, and no class with classid XXX
exists, then the application must send to the browser an application-specific reasonably-styled error page that displays a "no class with classid XXX exists" error message.
A non-existing classid error also could occur if (1) the browser sends a "class overviews" query 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 sends a "class details" query for classid 8321.
If the user directly (that is, not through the primary page) commands the browser to fetch a secondary page using a URL that is missing the classid=XXX
name/value pair, or that has a classid=XXX
name/value pair in which XXX
is missing, then the application must send to the browser an application-specific reasonably-styled error page that displays a "missing classid" error message.
If the user directly (that is, not through the primary page) commands the browser to fetch a secondary page using a URL has a classid=XXX
name/value pair, and XXX
is not an integer, then the application must send to the browser an application-specific reasonably-styled error page that displays a "non-integer classid" error message.
If the database cannot be opened when the browser sends a query, then the application must write a descriptive error message — the one contained within the thrown exception object — to its stderr
, and send to the browser an application-specific reasonably-styled error page that contains the generic "A server error occurred. Please contact the system administrator." error message.
If the database is corrupted when the browser sends a query, then the application must write a descriptive error message — the one contained within the thrown exception object — to its stderr
, and send to the browser an application-specific reasonably-styled error page that contains the generic "A server error occurred. Please contact the system administrator." error message.
You have no control of the database used by the reference application. So it won't be possible for you to run the reference application when its database cannot be opened or has been corrupted. So it won't be possible for you to observe the page generated by the reference application in those cases. And so this screenshot shows that page:
We'll cover software testing techniques in lectures later in the semester. In the meantime, to test your work on this assignment it will be sufficient to rely upon (1) your knowledge of testing from the COS 217 course, and (2) this A Software Testing Taxonomy document.
Test your application by (1) reviewing this assignment specification thoroughly, making sure that your application conforms to every aspect of it, and (2) comparing the behavior of your application with the behavior of the reference application.
Focus on boundary (alias corner case) testing. Of course, you must make sure that your application handles normal data. But you also must make sure that your application handles unusual data: courses that have multiple cross-referenced departments/numbers, long titles, long descriptions, multiple professors, no professors, and so forth. You also must make sure that your application handles errors: bad manually-entered URLs, missing database, corrupted database, and so forth.
Hint: Make sure your application handles queries for these titles: "Independent Study"
, "Independent Study "
, "Independent Study "
, " Independent Study"
, and " Independent Study"
.
Also focus on statement (alias coverage) testing. Your tests should cause every statement of your application to be executed.
You're encouraged, but not required, to use the Python coverage
tool to generate a coverage report showing which lines of your application have and have not been executed by your tests. These are the steps:
debug=True
argument within the call of app.run()
. The Python coverage tool doesn't work with Flask in debug mode.python -m coverage run -p --source=. runserver.py someport
..coverageX
file.python -m coverage combine
to combine the coverage reports generated by steps 2 through 4 into one large coverage report in a file named .coverage
.python -m coverage html
to use the .coverage
file to generate a human-readable report as a set of HTML documents in a directory named htmlcov
.htmlcov/index.html
to check the report.htmlcov
directory should show that 100% of your application's lines were executed. If the report doesn't show 100% coverage, then revise your testing plan accordingly, delete the .coverage
file and the htmlcov
directory, and repeat steps 2 through 8.python -m coverage run -p runserver.py someport
creates a process that should loop infinitely. On a MS Windows system, the easiest way for you to kill that process is by typing Ctrl-c, Ctrl-break, or whatever works on your particular computer.It's more difficult to automate the testing of the Assignment 3 application than it was to automate the testing of the Assignment 1 application. Generalizing, it's more difficult to automate the testing of web applications than it is to automate the testing of applications that are purely textual.
Nevertheless, it's possible to automate the testing of many web applications using a library named Selenium. A Python program that uses Selenium can send commands to a browser, which in turn can communicate with your web application. The programs /u/cos333/Asgt3Solution/testreg.py
and /u/cos333/Asgt3Solution/testregdetails.py
use Selenium in that way.
Your first step should be to study those programs. Note that for testreg.py
and testregdetails.py
to work with your application, your code must conform to some constraints. Specifically, your primary 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
.input
element that has an id
attribute whose value is submitButton
.table
element that has an id
attribute whose value is overviewsTable
.Your secondary page must contain:
table
element that has an id
attribute whose value is classDetailsTable
.table
element that has an id
attribute whose value is courseDetailsTable
.After you've studied the test programs, issue these commands on courselab make your own copies of them:
cd yourProjectDirectory cp /u/cos333/Asgt3Solution/testreg.py . cp /u/cos333/Asgt3Solution/testregdetails.py .
Then issue these commands on courselab to make sure you know how to run the test programs:
cd yourProjectDirectory python testreg.py -h python testregdetails.py -h
Note that each program requires you to provide a "mode" command-line argument which must be either normal
or headless
.
Then issue commands such as these on courselab to automate testing of your application with respect to its generation of "primary" pages:
# Run your application on courselab01 at port 55555. python runserver.py 55555 # Run testreg.py, which will run Firefox, which will communicate # with your application. Save the output to out1. python testreg.py http://courselab01.cs.princeton.edu:55555 normal > out1 2>&1 # Run testreg.py, which will run Firefox, which will communicate # with the reference application. Save the output to out2. python testreg.py https://cos333reg1.cs.princeton.edu normal > out2 2>&1 # Compare the contents of out1 and out2. diff out1 out2
In those commands you can replace courselab01
with courselab02
, 55555
with any reasonable port, and normal
with headless
.
diff
command should generate no output, thus indicating that the out1
and out2
files are identical.
Thereafter, edit your copy of testreg.py
to add additional tests, and repeat that command sequence
Similarly, issue commands such as these on courselab to automate testing of your application with respect to its generation of "secondary" pages:
# Run your application on courselab01 at port 55555. python runserver.py someport # Run testregdetails.py, which will run Firefox, which will # communicate with your application. Save the output to out3. python testregdetails.py http://courselab01.cs.princeton.edu:55555 normal > out3 2>&1 # Run testregdetails.py, which will run Firefox, which will # communicate with the reference application. Save the output to out4. python testregdetails.py https://cos333reg1.cs.princeton.edu normal > out4 2>&1 # Compare the contents of out3 and out4. diff out3 out4
In those commands you can replace courselab01
with courselab02
, 55555
with any reasonable port, and normal
with headless
.
The diff
command should generate no output, thus indicating that the out3
and out4
files are identical.
Thereafter, edit your copy of testregdetails.py
to add additional tests, and repeat that command sequence.
diff out1 out2
command, then widen your terminal window and issue the command diff -y out1 out2
. The output will be a side-by-side display of the two files, with markers denoting lines that differ. Same for diff out3 out4
.
testreg.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. If you're working on the assignment alone, then the first line of your readme
file must contain your Princeton netid, and nothing else. If you're working on the assignment with a teammate, then the first line of your readme
file must contain your Princeton netid and your teammate's Princeton netid with an underscore between the two, and nothing else. That information in that format will help us to automate the process of determining who worked with whom.
Thereafter 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 on courselab
using these commands:
submit 3 readme submit 3 runserver.py submit 3 allFilesComprisingYourApplication
You're not required to submit your testreg.py
or your testregdetails.py
. But if you do submit them, then your grader will comment on them briefly.
As noted above in the Rules section, it must be the case that either you submit all of your team's files or your teammate submits all of your team's files. (It must not be the case that you submit some of your team's files and your teammate submits some of your team's files.) You may submit multiple times; we'll grade the latest files that you submit.
Please follow the rules on what to submit. It will be a big help to us if you get the filenames right and submit exactly what's asked for. Thanks.
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:
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.
If your code fails the tests on some particular functionality, then your grader won't be able to inspect your code manually to try to assign partial credit for that functionality. So please make sure your code behaves properly.
Copyright © 2024 by Robert M. Dondero, Jr.