Car Rental
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.
- 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.)
- Employees must log in with an account name and password.
Feel free to swipe much of this code from the Counter 4 example.
- 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.)
- We will need (at least) two database tables.
- 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.
- 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.
- 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.
- Perhaps views for login, inventory, and inventory detail,
and for renting, including filling in a renter's details.
- 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.
- Inventory detail shows a specific car in detail, presumably
reachable from the inventory.
- The renting fill-in will require name, address and phone number, and
the return date.
- Keeping a renter's info in the inventory table
is acceptable, though a better db design would have a renters or customers
table.
- 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.
- 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.
- Create a directory (or a folder if that's all you your system
has) for flask. I
creatively called mine flask.
- Create a sub-directory for the project itself. In another spurt of
creativity, mine is rental.
- Inside rental, I made another directory, static (required name),
for any static content (css, js, images, etc.) I may want to create.
- Also inside rental, I created a __init__.py, which
initializes the Python module which rental is.
- You might look at the init file in the Chat example. I swiped the
imports, except the CORS, which isn't needed here.
- 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.)
- 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.
- Now, I created home.py, which adds one simple view.
-
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.
- 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.)
- 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.
- 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.)
- 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.
- The fields you need are
- Description of the car.
- Size class of the car.
- Name of the renter
- Address of the renter
- Phone number of the renter
- Daily rental price
- Number of days rented
- 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.
-
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.
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.
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
- I swiped the base and counter templates from Counter 4, and placed them
in a new directory templates inside inven.
- 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.
- For the login form, I removed the action attribute so the form
submits back to the URL it came from.
- 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.
- The existing home.py becomes the login page.
- It now has two view functions, distinguished by method (same URL).
This technique is used in Counter 4 registration.
- When reached by GET, the session is initialized as logged out,
and the login template is rendered.
- 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.
- I followed the pattern in gen.logo in
the counter to
check the password.