Skip to content

Latest commit

 

History

History
127 lines (91 loc) · 4.88 KB

23-finishing-touches.md

File metadata and controls

127 lines (91 loc) · 4.88 KB

今天我們要完成這個網站。

第一件事情,是要讓使用者可以修改點餐內容。其實需要用到的東西我們之前都提過了,只需要實行而已。在 EventDetailView 新增一個 method:

# events/views.py

class EventDetailView(LoginRequiredMixin, DetailView):
    # ...
    def get_order(self, user):
        try:
            order = Order.objects.get(user=user, event=self.get_object())
        except Order.DoesNotExist:
            order = None
        return order
    # ...

注意這裡同樣要使用 get_object,因為我們不只會在 get 時使用這個 method。

接著在 get_context_data 中,用它取出當下使用者的 order:

def get_context_data(self, **kwargs):
    # ...
    order = self.get_order(user=self.request.user)
    # 取代原本初始化 OrderForm 的那行。
    order_form = OrderForm(instance=order)
    # ...

這樣就可以在使用者已經有 order 時(get_order 會回傳非 None 的值),在 form 中直接顯示原本 order 的內容。

接著,如果使用者有更新,我們同樣要先嘗試找到已有的 order;如果找不到才新增:

def post(self, request, *args, **kwargs):
    order = self.get_order(user=request.user)
    form = OrderForm(request.POST, instance=order)
    # ... 後略。

就這麼簡單!現在使用者在點過餐後,下面的表單會直接顯示他之前點餐的內容。如果使用者再次送出表格,就會更新既有的點餐內容,而不是新增(而造成錯誤)。

現在我們可以建立活動,但除了直接給活動網址外,根本沒辦法讓其他使用者看到現在的活動。所以接著我們要在首頁加上一個連結,讓大家能進來點餐。首頁上的連結應該動態指向最新的活動,但「最新的」是哪一筆?在我們這個例子裡,會是 primary key 最大的那一筆,也就是最近新增的活動。在 SQL 中,我們可以用 MAX 函式來取得這個最大值,但 Django 的 ORM 要怎麼做?

有兩種做法。第一種是用排序,然後取出最新一筆:

current_event = Events.obejcts.order_by('-pk')[0]

order_by method 的作用和 SQL 的 ORDER BY 類似,但當你要指定排序時,不是使用 ASCDESC 修飾,而是使用 - 號。這裡的 -pk 代表使用 primary key 反向排序(同 DESC);如果是 pk,則會是正向排序(同 ASC)。

但這有個問題。如果沒有第一筆,那麼上面的程式會產生 IndexError。當然我們可以用一個 try-catch block 把它包起來,不過因為這種格式實在太常見,Django 提供了一個方便的 method 來處理:

current_event = Events.obejcts.order_by('-pk').first()

或者,如果要更直觀:

current_event = Events.obejcts.order_by('pk').last()

另一種做法是把 EventMeta 改成下面這樣:

class Meta:
    get_latest_by = 'pk'

接著你可以這樣取出最新一筆:

current_event = Events.obejcts.latest()

這個做法的好處是,如果你的 model 有很明確的排列順序(例如 blog post 或新聞會依發表時間排序等等),這個做法可以讓你集中邏輯,方便讓你未來知道「最新」的意義,在維護上與城市的語義都比較清楚。另一方面,firstlast 就只是單純用來告知「目前這個 query set」的狀態,適合用在特定的排序上。[註 1]

由於 event 使用 primary key 排序是很普遍的做法,不是特殊排序,我們這裡使用後者。修改 home view,取出合適的 event instance 來使用:

# pages/views.py

from events.models import Event

def home(request):
    try:
        current_event = Event.objects.latest()
    except Event.DoesNotExist:
        current_event = None
    return render(request, 'pages/home.html', {'current_event': current_event})

然後在 template 中使用:

{# pages/templates/pages/home.html #}

{% extends 'pages/base.html' %}

{% block body %}
{{ block.super }}

<div class="container">
  {% if current_event %}
  <div class="row">
    <div class="col-md-6 col-md-offset-3">
      <div class="text-center">
        <h1>今天吃:{{ current_event }}。</h1>
        <a href="{{ current_event.get_absolute_url }}" class="btn btn-primary btn-lg btn-block">快點餐!</a>
      </div>
    </div>
  </div>
  {% endif %}
</div>

{% endblock body %}

完成!這樣網站就做得差不多了。明天我們會進入最後階段,教你怎麼 deploy 這個網站,包括 PaaS(用 Heroku 為例)與一般的 Un*x Server(Windows 就⋯⋯再看看)。


註 1:另外要注意一點:latest(與它的兄弟 earliest)會在找不到內容時產生 IndexError,不會像 firstlast 那樣回傳 None。所以使用這兩個 methods 時要確認資料庫中有對應值。