Princeton University
COS 333: Advanced Programming Techniques

Assignment 3: A Registrar Application: Web Version 1


Purpose

The purpose of this assignment is to help you learn or review server-side web programming.


Rules

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.


Your Task

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

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.


Reference Application

A reference application is running at https://cos333reg1.cs.princeton.edu. You can run the reference application by browsing to that address.

Of course the reference application delivers HTML code to your browser. You could use your browser's 'view source' feature to examine that code. But don't do that! That is, don't examine the code that the reference application delivers to your browser. Doing that would decrease the value of the assignment for you, and would be a violation of course policies.


The 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 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.

Hint: Your application must be stateful to implement the page link back to the primary page.

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.

Note: By default, the Firefox browser on Mac may not handle tabbing between input elements and links. To enable tabbling, in Firefox click on Preferences -> General and make sure the Always use the cursor keys to navigate within pages check button is unchecked. Then, in Mac System Preferences, click on Keyboard -> Shortcuts and make sure the All Controls radio button is checked.

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!


Error Handling

Your application must be robust. It must handle the following errors:

Wrong command-line argument count

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.

Non-integer port

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.

Non-existing classid

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.

Missing classid

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.

Non-integer classid

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.

Database cannot be opened

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.

Corrupted database

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:



Testing

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.

Boundary Testing

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".

Statement Testing

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:

  1. Delete the debug=True argument within the call of app.run(). The Python coverage tool doesn't work with Flask in debug mode.
  2. Launch your application server via the command python -m coverage run -p --source=. runserver.py someport.
  3. Use a browser to interact with your application, thereby creating a .coverageX file.
  4. Exit your application server.
  5. Repeat steps 2 through 4 as desired.
  6. Issue the command 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.
  7. Issue the command 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.
  8. Browse to htmlcov/index.html to check the report.
  9. The files in your 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.
If you're using a MS Windows local computer...

Issuing the command 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.

However, on at least some MS Windows computers, killing the process in that abrupt way suppresses generation of the coverage report. So you may not be able to generate your coverage report on your local MS Windows computer. You may need to generate your coverage report on courselab.


Test Automation

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:

Your secondary page must contain:

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.

The 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.

Hint: If you have trouble understanding the output of the 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.

Important note: The 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.


Submission

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.


Grading

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:

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.