CS50 problem set 7作業回顧

陳雁智 (Marat Y. C. Chen)
Manjeaneer
Published in
7 min readNov 8, 2017

--

pset 6 認識 python 及 flask 後, pset7 加入 CSS 及資料庫 (database) 操作,完成類似股票交易平台的網址,基本要求有以下功能:

  1. 註冊/登錄
  2. 根據股票代碼查詢股價並執行買賣
  3. 呈現交易記錄及目前股票組成

完成作業基本要求倒不難,跟 pset 6 的難度相近,多了讀取或寫入 database ,將幾個主要函式、對應的 html、 及 database 的操作劃出來就可以感受到 資料庫幾乎是這類系統的中心,也突顯出 SQL injection 的危害程度

另外,CSS 的好處在這裡突顯,sytle.css 幾乎控制所有頁面元素的屬性,如顏色、線條粗細、間距等,需要修改風格時只需要更動單一檔案既可套用到所有頁面

Scheme of 2017 cs50 pset 7 “cs50 finance”

cs50 團隊避免過早提及 javaSciprt, 所以 cs50 finance 架構中,幾乎每個 route 都對應到 render 出一個 html ,也就是每個操作完成都需要重新讀取頁面,但也埋下一個小問題

除了基本要求外,還需要額外完成以下其中一項:

  1. 更改密碼
  2. 充許增加現金 (預設登錄為 $10,000)
  3. 能在股票組成頁面 (index) 執行買賣,不需要鍵入股票代碼

很好,看起來第三個最難,選它,於是實作就卡在主要兩個部分:

  1. 為了達成第三點,如何將代入目前頁面資訊做為 request 內容一起帶入 request 到 server 端 ( 不使用 javaScript )
  2. 課程提供 cs50 方便操作 database,實務上可能還是用 ORM 或其它方式,花了一點時間將 database 相關的操作都用 SQLAlchemy 重新寫過

關於第一點,筆者的作法是新增股數輸入框,買與賣兩個按鈕,將它們都包入同一個 form,及新增一條 trade route 處理,大致上如以下 pseudo code:

<form action="{{ url_for('trade') }}" method="post">
...(skip)...
<td>{{ jinja_variable_for_symbol }}<input type="hidden" name="symbol" value="{{ jinja_variable_for_symbol }}" /></td>
// an hidden put field whose value is replaced by jinja variable

<td><input name="shares" type="number"/></td>
// an input field for fill in the shares to be sold or bought

<td><span><button type="submit" name="operation" value="buy">Buy</button></span></td>
// operation as the parameter name whose value is buy

<td><span><button type="submit" name="operation" value="sell">Sell</button></span></td>
// operation as the parameter name whose value is sell
...(skip)...
</form>

為了代入既有的股票代碼 (symbol) 到表格 (form) 遞交 (submit) 的 POST request,這次使用的方式是將股票代碼欄位也設定成 input 、值為 symbol 、但屬性為 hidden,在處理的 trade route 裡再透過 request.form.get 取出各個值 (symbol, share, operation(buy/sell)) 做交易處理完成

SQLAlchemy 心得

目前歸納下來不會寫壞掉的幾個方式:

  1. db = SQLALchemy(app) 先做,將 db 這個物件建出來,再做 db.create_all()db.session.query()操作,或是設計新的類別 (class)
  2. 各個表格依 schema 都需要一個對應的 class,表格 (table)名稱用 table_name 設定,要注意的是 SQLAlchemy 要求至少一個 primary key ,沒有設定的話無論是建立表格或查詢 (query) 都會失敗
class User(db.Model):
__tablename__ = 'users'
// tablename for queries in database

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
// key value of the table

username = db.Column(db.String(80), unique=True, nullable=False)
hash = db.Column(db.String(120), unique=True, nullable=False)
cash = db.Column(db.Float, nullable=False, default=10000)
def __repr__(self):
return '<User %r>' % self.username
new_User = User(username="abc", hash=pwd_context.hash("password"))
db.session.add(new_User)
db.session.commit()

2. db.session.add(new_User) 還不會把資料寫進資料庫,只是先註冊 (register) 這個操作,要到 db.session.commit() 成功完成才能算是操作成功,若一直沒有看到資料被寫入或更新,最好先檢查是否有做 commit

3. User.query() 可以執行查詢,這個方式限制的方法還蠻多,大部分還是使用 db.session.query(User) 執行,在加上 filter_byorder_by 的操作通常不會出錯

4. ascdesc 的操作需要先以 from sqlalchemy.sql import func 方式引入 func: db.session.query(User.name).orderby(User.id.desc())

5. count 的操作: db.session.query(User.username).count()

6. limit 的操作: db.session.query(User.username).limit("number")

將 SQL 相關的部分重寫來熟悉 ORM 是個還不錯的練習,畢竟實務上手刻 SQL statement 出錯機會還蠻高,加上檢查的成本,不太經濟,況且基本防止 SQL injection 的保護也會做好,但實務上遇到調校 query 的時候還是得仰賴專業 DBA 的協助,必要時還是得手刻,不過機率相對小就是

接下來就是最後一個 web programming 的作業,加上 javascript, jQuery, 跟 ajax,讓學員對熟悉前後端的基本結構,在 javaScript 上踩了不少雷,下期一起補充一下 C, Python, javascript 對變數範圍及引用的基本差異

--

--

陳雁智 (Marat Y. C. Chen)
Manjeaneer

project manager/savvy programmer/marathon runner/critical reader