So far, we have coded a few of our requirements from front to back. We built the view layer using CherryPy. Next, we configured security to protect the site. Then we fully implemented one of the customer features so that it stored new accounts in the database.
Coding more features follows a similar pattern. The order the steps are executed are a matter of taste. It is possible to code the entire back end using automated testing, followed by skinning it with a web layer. In our case, we used the more demonstrative style of building the interface first, and then fleshing out the backend later. Either way, it is easy to iterate through and see how Spring Python's IoC container kept the security, view, and controller logic nicely decoupled.
Now let's fast forward development to the point where all the customer features are implemented.
We updated the index page in SpringBankView
, so that it lists the customer's current accounts as well as listing the other functions available.
In this example, Alice already has two accounts created. In the previous version, they weren't listed, but now they are.
@cherrypy.expose def index(self, message=""): results = """ <h1>Welcome to SpringBank!</h1> <p> %s <p> """ % message if "ROLE_CUSTOMER" in SCH.getContext().authentication.granted_auths: results += """ <h2>Existing Accounts</h2> <table border="1"> <tr><th>Account</th><th>Description </th><th>Balance</th></tr> """ for account in self.controller.get_accounts( SCH.getContext().authentication.username): results += ''' <tr> <td>%s</td><td><a href="history?id=%s">%s</a> </td><td>%s</td> </tr> ''' % (account["id"], account["id"], account["description"], account["balance"]) results += """ </table> <h2>Customer Options</h2> <ul> <li><a href="openAccount">Open Account</a></li> <li><a href="closeAccount">Close Account</a></li> <li><a href="withdraw">Withdraw Money</a></li> <li><a href="deposit">Deposit Money</a></li> <li><a href="transfer">Transfer</a></li> </ul> """ results += """ <a href="logout">Logout</a href> """ return results
As you can see, wedged into the middle is a for-loop that generates rows in an HTML table, listing the accounts the user owns. get_accounts
is delegated to the controller.
def get_accounts(self, username): return self.dt.query(""" SELECT id, description, balance FROM account WHERE owner = ? AND status = 'OPEN'""", (username,), DictionaryRowMapper())
Each row includes a link to view the history for that account, and at the bottom of the screen are links to the other new functions.
We altered the back end of openAccount
by marking new accounts with a state of OPEN
.
def open_account(self, description): self.dt.execute(""" INSERT INTO account (description, balance, owner, status) VALUES (?, 0.0, ?, 'OPEN')""", (description, SCH.getContext().authentication.username)) self.log("TX", self.get_latest_account(), "Opened account %s" % description)
We also added the extra step of writing a transaction log entry. To support writing the log entry, the controller must retrieve the latest account created for this customer.
def get_latest_account(self): return self.dt.query_for_long(""" SELECT max(id) FROM account WHERE owner = ? AND status = 'OPEN'""", (SCH.getContext().authentication.username,))
open_account
, along with several of the other controller operations uses the following code to write log entries.
def log(self, type, account, message): self.dt.execute(""" INSERT INTO log (type, account, message) values (?, ?, ?)""", (type, account, message))
We added closeAccount
to SpringBankView
to let the customer close an existing account if its balance is zero.
@cherrypy.expose def closeAccount(self, id=""): if id != "": self.controller.close_account(id) raise cherrypy.HTTPRedirect("/?message= Account successfully closed") results = """ <h2>Close an Account</h2> <form method="POST" action=""> <table border="1"> <tr><th>Account</th></tr> """ for account in self.controller.closeable_accounts( SCH.getContext().authentication.username): customer features, banking applicationcloseAccount operation, addingresults += """ <tr><td><a href="closeAccount?id=%s">%s - %s</a> </td></tr> """ % (account["id"], account["id"], account["description"]) results += """ </table> </form> <a href="/">Cancel</a> """ return results
Looking up closeable_accounts
as well as completing the close_account
operation is delegated to the controller.
def closeable_accounts(self, username): return self.dt.query(""" SELECT id, description FROM account WHERE owner = ? AND balance = 0.0 AND status = 'OPEN'""", (username,), DictionaryRowMapper()) def close_account(self, id): self.dt.execute(""" UPDATE account SET status = 'CLOSED' WHERE id = ?""", (id,)) self.log("TX", id, "Closed account")
Opening a new account causes a row to be inserted into the ACCOUNT table. However, closing an account does not mean deleting the same row. Instead, its status is updated to CLOSED.
We added withdraw
to SpringBankView
to let the customer withdraw money from an existing account.
@cherrypy.expose def withdraw(self, id="", amount=""): if id != "" and amount != "": self.controller.withdraw(id, float(amount)) raise cherrypy.HTTPRedirect("/?message=Successfully withdrew %s" % amount) results = """ <h2>Withdraw Money</h2> <form method="POST" action=""> <table border="1"> <tr> <td>Amount</td> <td><input type="text" name="amount"/></td> </tr> <tr><td colspan="2"> """ for account in self.controller.get_accounts(SCH.getContext().authentication.username): customer features, banking applicationwithdraw operation, addingresults += """ <input type="radio" name="id" value="%s">%s - %s</input><br/> """ % (account["id"], account["id"], account["description"]) results += """ </td></tr> <tr><td colspan="2"><input type="submit"/></td></tr> </form> """ return results
When the user clicks submit, withdraw
is delegated to the controller.
def withdraw(self, id, amount): self.dt.execute(""" UPDATE account SET balance = balance - ? WHERE id = ?""", (amount, id)) self.log("TX", id, "Withdrew %s" % amount)
We added deposit
to SpringBankView
to let the customer deposit money into an existing account.
@cherrypy.expose def deposit(self, id="", amount=""): if id != "" and amount != "": self.controller.deposit(id, float(amount)) raise cherrypy.HTTPRedirect("/?message=Successfully deposited %s" % amount) results = """ <h2>Deposit Money</h2> <form method="POST" action=""> <table border="1"> <tr> <td>Amount</td> <td><input type="text" name="amount"/></td> </tr> <tr><td colspan="2"> """ for account in self.controller.get_accounts(SCH.getContext().authentication.username): results += """ <input type="radio" name="id" value="%s">%s - %s</input><br/> """ % (account["id"], account["id"], account["description"]) results += """ </td></tr> <tr><td colspan="2"><input type="submit"/></td></tr> </form> """ return results
When the user clicks submit, deposit
is delegated to the controller.
def deposit(self, id, amount): self.dt.execute(""" UPDATE account SET balance = balance + ? WHERE id = ?""", (amount, id)) self.log("TX", id, "Deposited %s" % amount)
We added transfer
to SpringBankView
to let the customer transfer money from one account to the other.
@cherrypy.expose def transfer(self, amount="", source="", target=""): if amount != "" and source != "" and target != "": self.controller.transfer(amount, source, target) raise cherrypy.HTTPRedirect("/?message=Successful transfer") results = """ customer features, banking applicationtransfer operation, adding<h2>Transfer Money</h2> <form method="POST" action=""> <table border="1"> <tr> <td>Amount</td> <td><input type="text" name="amount"/></td> </tr> """ accounts = self.controller.get_accounts(SCH.getContext().authentication.username) for param in ["source", "target"]: results += '<tr><td>%s</td><td>' % (param[0].upper() + param[1:]) for account in accounts: results += """ <input type="radio" name="%s" value="%s">%s - %s</input><br/> """ % (param, account["id"], account["id"], account["description"]) results += "</td></tr>" results += """ <tr><td colspan="2"><input type="submit"/></td></tr> </form> """ return results
When the user clicks submit, transfer
is delegated to the controller.
def transfer(self, amount, source, target): self.withdraw(source, amount) self.deposit(target, amount)
transfer
conveniently re-uses withdraw
and deposit
.
The last customer facing feature added was the ability to view an account's history.
@cherrypy.expose def history(self, id): results = """ <table border="1"> """ for entry in self.controller.history(id): results += """ <tr><td>%s</td><td>%s</td></tr> """ % (entry["date"], entry["message"]) results += """ <table> """ return results
When the user clicks submit, history
is delegated to the controller.
def history(self, id): return self.dt.query(""" SELECT type, date, message FROM log WHERE log.account = ? AND log.type = 'TX'""", (id,), DictionaryRowMapper())