CSc 445 Asst 6

Car Rental

Assigned
Due

Nov 14
90 pts
Dec 10

The last assignment remains something of a work in progress. Do check back for changes.

Original Notes

This is a summary of what we discussed in class as an initial definition of the project, with some elaboration. And mostly complete sentences.

  1. The web service is for a car rental service. We're creating a service to be used behind a physical counter by the employees of the rental company. (Feel free to extend the project to a customer site later if you need something to do over break. And are a true nerd.)
  2. Employees must log in with an account name and password. Feel free to swipe much of this code from the Counter 4 example.
  3. Creation of logins and setting passwords is done from the command line for now. Again, you can swipe this code from the example. (Allowing employees to change the password online would be good if you want to add it. See entry #1.)
  4. We will need (at least) two database tables.
    1. One for logins, which would probably hold the employee's human name along with with login creds. This will be much like the Counter 4 table.
    2. Inventory. This will need to hold a description of the car, its size class (say economy, medium, and large, or some such), and its status (rented or available). The simplest thing would be to have the rental price in this table.
    3. A nice elaboration would be to have the class of each car in the inventory table, and another table to just map the class to the price.
  5. Perhaps views for login, inventory, and inventory detail, and for renting, including filling in a renter's details.
    1. The inventory should allow filtering on at least class of car, and and status. Each car which is rented should have a return button entry.
    2. Inventory detail shows a specific car in detail, presumably reachable from the inventory.
    3. The renting fill-in will require name, address and phone number, and the return date.
  6. Keeping a renter's info in the inventory table is acceptable, though a better db design would have a renters or customers table.
  7. For now, when the return date is entered, let's just fake it by giving a number of days. Handling dates can get complicated, so let's avoid that.
  8. For now, just add or remove cars from the command line. Avoids yet another view.

How I Started

To just get something up, I did the following. I have Flask installed on my desktop, which is a prerequisite. I haven't tried to run anything on Sandbox yet, just working locally with the Flask install and familiar tools.

  1. Create a directory (or a folder if that's all you your system has) for flask. I creatively called mine flask.
  2. Create a sub-directory for the project itself. In another spurt of creativity, mine is rental.
  3. Inside rental, I made another directory, static (required name), for any static content (css, js, images, etc.) I may want to create.
  4. Also inside rental, I created a __init__.py, which initializes the Python module which rental is.
    1. You might look at the init file in the Chat example. I swiped the imports, except the CORS, which isn't needed here.
    2. I made a factory function, also called create_app, which constructs and returns a Flask app. Unlike the example, I didn't attempt any configuration. (That can go in later as needed.) Just app = Flask(__name__) and we're good. (I guess the create_app name must be required, since I can't see how else the test server can know what to call.)
    3. After creating the app, the factory function adds a single view which I called home. I used the same two-line pattern that Chat uses for each of the views it adds (not counting the test /hello view). I import the setup method from a file home.py, and called its setup method (next step) sending the app object. This is the same way Chat adds the poll and add views.
  5. Now, I created home.py, which adds one simple view.
    1. My home.py is very short, containing an initialization method which adds a view function with @app.route("/"), much like some of the earlier Flask examples. The initialization function may have any name you like, but it is the name called from __init__. The add.py and poll.py methods in Chat each have methods like that at the bottom.
    2. The actual view method in home.py is also simple, just returning an HTML document as a string. (Chat follows the same pattern, but its methods are longer, and return JSON rather than HTML.)
  6. Now, from the flask directory (above rental or whatever it might be called), I can run
    flask -app rental run
    and load the URL it states in the browser to see the page.

One useful note: The command

python -m py_compile whatever.py
will syntax check your Python without running it. That can be of use occasionally.

On to the database!

Database Setup

Here's how I created the basic database setup. This is all coding and command-line stuff. Didn't add any view, yet.

  1. I copied the schema.sql and db.py from Counter 4 into the rental directory. For the schema, I kept the users table, but removed the view field. (As you recall, I had something in mind for it but never used it. Don't even really what, anymore.)
  2. I added a table for inventory. I also added tables for renters and one listing all the classes and prices. This seemed to be a better design, but keeping everything in just the inventory table is fine. This is not a database class, and we haven't taken a lot of time with the details required. Unless you have some experience with SQL, stick with the two tables, one for users and one for inventory. Maybe update it later if you have time and are still able to care.
  3. The fields you need are
    1. Description of the car.
    2. Size class of the car.
    3. Name of the renter
    4. Address of the renter
    5. Phone number of the renter
    6. Daily rental price
    7. Number of days rented
    8. Though you may be able to do without, an id number for each car might be helpful. I used an AUTOINCREMENT, of which you can see an example in the schema for Chat.
  4. If using two tables, all this goes in inventory. I used four, so the fields are broken up between them, and the inventory table makes foreign key references into the other tables.

    The natural way to represent the sizes would be with an enumerated type, which some database systems support. Sqlite does not, but here is a suggested alternative. In my four-table solution, the sizes are the keys of my prices table, and are constrained by its contents.

    My types are generally INTEGER and TEXT. For the price, I used the fancy SQL DECIMAL(5,2). An alternative would be REAL. From what little I've read about it, sqlite is very permissive about types, so pretty much anything that smells like a number work fine. More here.

  5. After constructing my schema, I modified the create_app in __init__.py to set the database location in the app after creating it, as done in the Chat example. You'll need to add import os to get the join method.

    Also, swipe this code that appears in init of each of our examples that use a database:
    try: os.makedirs(app.instance_path) except OSError: pass
    (I forgot this until after I had already created flask/instance from the command line, and was wondering what I had forgotten since I hadn't needed to before.) I also updated the init file to import and run the initialization from db.py. See either Counter 4 or Chat for example.

  6. Now, the existing db functions in the swiped code can work. Go to the flask directory (above rental) and use
    flask --app rental init-db
    and it should initialize your database. Or report a the syntax error in or schema file, which you can then fix. You can also create users and set passwords with the exiting commands. (Note: I have seen a warning on one of the RE's I use for checking passwords. A warning only, so I haven't run it down yet. You might see it.)
    Your database file will have been created inside flask/instance. You can view the database by visiting the directory and running sqlite3 mydatabase.sqlite (or whatever file name you used). To list your tables, use the .table command, and select * from tablename to see its contents.
The assignment does not require a way to add inventory from the web site (though that's a fine thing to have if you find time). You can add entries to inventory using sqlite3 to issue SQL commands to the database. I added small command to add (unrented) cars to the inventory:
flask$ flask --app rental add-car Description: Blue Toyota Sienna Size class (small, medium, large): large Inventory Blue Toyota Sienna (large) added
To do this, I Followed the pattern of db.py in Counter 4 to create a CLI command with the two arguments shown. The body of the method performs the SQL commands (rather than calling other methods as the example). I also updated the init_app method at the bottom to add my command to the app. The existing add_user method, and some code in the Chat have examples of the SQL insert command similar to what is needed here.

Next: Making it list the inventory.

Well, not Yet. First...

Integrating the Login

  1. I swiped the base and counter templates from Counter 4, and placed them in a new directory templates inside inven.
  2. I renamed counter.tmpl to login.tmpl. I cut a little from the base and a lot from login. Counter doesn't mention state, just HTML boiler plate and the optional error display at bottom. Login just presents the form for creds, and removes basically everything else.
  3. For the login form, I removed the action attribute so the form submits back to the URL it came from.
  4. I created a stub inventory view. New file inven.py which contains a single view, that for present is just an HTML string that says it's a stub. Give the file a proper init method and have __init__.py call it, so inven can join the party.
  5. The existing home.py becomes the login page.
    1. It now has two view functions, distinguished by method (same URL). This technique is used in Counter 4 registration.
    2. When reached by GET, the session is initialized as logged out, and the login template is rendered.
    3. When reached by POST, the login data is checked. If bad, the login template is rendered again with an error message. If not, the browser is forwarded to the inventory listing, presently a stub.
    4. I followed the pattern in gen.logo in the counter to check the password.