2019年4月19日 星期五

[GAE] 網站的目錄規劃

  雖然標上GAE,但這篇的內容其實和Google App Engine沒有太大的關聯,而是可以更廣泛的應用在各種架網站的狀況,這篇文章是提供建議,如何規劃網站的目錄結構,讓我們的專案更加有序與方便維護。(例子是使用Python webapp2這個架網站的框架,然後部署在App Engine上)

  我主要是參考 Learn to build scalable web applications 這系列Section 3的部分,當初我看得時候可說是大受感動(?),第一次感受到專案目錄規劃的重要,我寫程式碼時因為大多只是寫來玩玩練習,因此都隨便放,總之能import到就可,老實說對專案目錄怎麼規劃沒太深的見解,從這支影片看到講師如此認真的講解目錄怎麼規劃,覺得以後應該要更認真看待這部分,若寫一個比較大型的程式,要有好的開始真的就要有好的目錄規劃,因此決定來記錄一下,當然目錄怎麼規劃沒有正解,畢竟要依需求來決定,但下面的做法還是不錯值得參考借鏡。

  底下的文章有許多照搬的部分,所以其實大可直接去看影片 Section 3的部分,下面只是我的整理筆記。

好的目錄結構,能幫助我們管理與擴展,看起來也更加專業!
  總之,先來看看專案目錄吧!

/project
    — /app
          — __init__.py
          — home.py
    — /framework
          — __init__.py
          — request_handler.py
    — /models
          — __init__.py
    — /static
          — /css
          — /fonts
          — /js
          — /img
    — /templates
        — /commons
              — navbar.html
              — leftmenu.html
        — /home
              — home.html
        — main.html
    — app.yaml
    — router.py


  在專案第一層共有五個目錄,分別是 /app、/framework、/models、/static、/templates,與兩個檔案 app.yaml 和 router.py。
  1. /app:request handlers
  2. /framework:own custom request handler
  3. /models:model of data using in datastore
  4. /static:靜態檔案,例如 .css、.js、圖片媒體檔
  5. /templates:網頁html檔
  6. app.yaml:設定Google App Engine用
  7. router.py:URL對映


先來看 app.yaml 與 router.py。

app.yaml:設定環境與URL對映handler
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: /static
  static_dir: static  ## 對映到專案裡的static目錄

- url: /.*
  script: router.app

libraries:
- name: jinja2
  version: latest

router.py:URL對映handler (做了兩次URL對映)
from webapp2 import WSGIApplication
from webapp2 import Route

app = WSGIApplication(
    routes=[
        Route('/', handler='main.MainPage'),
    ]
)

再來看看 handler 處理者的部分。

/framework/request_handler.py:建立custom request handler類別供/app裡的handler繼承,並在裡面實作常用之邏輯,如此能有效減少duplicate code的狀況,像是在 render template時,我們都會做傳入參數動態繪製html後再回覆給Client端,在這裡我們就將這些動作統一包裝成 render(self, template, **kwargs),/app裡的handler就可以呼叫 self.render()做這一系列的動作。

from webapp2 import RequestHandler
import os
import jinja2

class OurRequestHandler(RequestHandler):
    template_directory = os.path.join(
     os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)),
        'templates')

    jinja_enviroment = jinja2.Environment(
     loader=jinja2.FileSystemLoader(template_directory)
    )

    def render(self, template, **kwargs):
     jinja_template = self.jinja_enviroment.get_template(template)
     html_from_template = jinja_template.render(**kwargs)

     self.response.out.write(html_from_template)

* os.path.join(os.path.dirname(__file__), os.pardir) 等同目前檔案之上一目錄 (pardir = parent directory)。使用 os.path.abspath() 是確保路徑符合環境使用的路徑格式,像是本地端是windows路徑會是以 "\" 隔開,但在Linux會是 "/"。

* **kwargs能代表多個參數,會是 dictionary 格式。

/app/home.py:處理請求,以功能分類,像home.py存放跟首頁有關的請求,account.py就可以放跟帳號有關的請求等。

from framework.request_handler import OurRequestHandler

class Home(OurRequestHandler):
    def get(self):
        self.render('home/home.html')

  /models就存放跟資料庫有關的model。
  最後是網頁繪製的部分。
  先來看看繪出來的home/home.html長怎樣。(先說一下,我不太會CSS)

  應該大家都看過類似這樣結構的網頁,上方總會有navbar顯示網站名或搜尋引擎,而左邊有菜單能點選,比較會變動的只有白色內容的部分,像這樣的設計其實並不需要每一頁都寫程式碼,我們可以透過jinja2引擎動態繪製並組合各html,如此也能減少duplicate code的問題。

/templates/main.html:主頁面,組合了 commons/navbar.html、commons/leftmenu.html,並預留一{% block content %}可以填充。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block Title %}{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head>
<body>
    {% include "commons/navbar.html" %}
    <div style="float: left">
 {% include "commons/leftmenu.html" %}
    </div>
    <div>
        {% block content %}
        {% endblock %}
    </div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</body>
</html>

* {% %}相關的都是跟jinja2有關的語法。

* "/static/..." 會從 app.yaml URL對映得知是要找專案裡static目錄下的檔案。

https://developers.google.com/speed/libraries/ 提供許多常用的 js library。

* .css與.js檔有名的library取得許多都有提供CDN可以請求,如此可以減輕我們server的流量,個人是覺得有就用,但缺點是因為是第三方管理檔案,做了修改我們都是沒法掌握的,所以為求安定還是載下來放進專案存取比較保險,但應該有名的CDN資料安定性都值得信任。

/templates/home/home.html:{% extends "main.html" %}相當貼上main.html的內容,然後可以將包在 {% block content %}{% endblock %}中的內容插入main.html的{% block content %}中。
{% extends "main.html" %}

{% block Title %}Home{% endblock %}

{% block content %}
<h1>Hello World!</h1>
{% endblock %}
/templates/commons/navbar.html:
<div style="background-color: pink; height: 100px;">
    This is navbar
</div>
/templates/commons/leftmenu.html:
<div style="background-color: blue; height: 600px">
    This is leftmenu
</div>

  大概就是這樣,整理下來再次覺得真的是很簡潔的寫法呢,可以減少許多原本需要重複程式碼的地方,第一次看到html組合起來的那部分,我還真有恍然大悟的感覺,原來可以這麼做!然後framework客製化request handler的部分也滿值得學習的,是平常寫程式就可以用到的概念。

沒有留言:

張貼留言