# 後端

# views

isDelivered 更新為 True 的同時,也要取得現在時間,更新 deliveredAt 欄位

# order_views.py
# 更新訂單運送狀態為:已運送
@api_view(['PUT'])
@permission_classes([IsAdminUser])
def updateOrderToDelivered(request, pk):
    order = Order.objects.get(id=pk)
    order.isDelivered = True
    # 取得現在時間
    timezone_taipei = pytz.timezone('Asia/Taipei')
    now = timezone.now().astimezone(timezone_taipei)
    order.deliveredAt = now
    order.save()
    return Response('訂單狀態已更新為:已運送')

# urls

# order_urls.py
path('<str:pk>/delivered/', views.updateOrderToDelivered, name='order-delivered'),

# Postman 測試

老套路,後端寫好 API 後,先用 Postman 測試看是否有成功回傳資料,一樣帶入擁有 admin 權限的 token 來檢視所有訂單資料

orders_postman

# 前端

# constants 定義常數

// orderConstants.js
export const ORDER_DELIVER_REQUEST = "ORDER_DELIVER_REQUEST";
export const ORDER_DELIVER_SUCCESS = "ORDER_DELIVER_SUCCESS";
export const ORDER_DELIVER_FAIL = "ORDER_DELIVER_FAIL";
export const ORDER_DELIVER_RESET = "ORDER_DELIVER_RESET";

# reducers

orderDeliverReducerorderPayReducer 類似,都是更新訂單狀態的 reducer

// orderReducers.js
// 管理員更新訂單運送狀態為 已運送
export const orderDeliverReducer = (state = {}, action) => {
 switch (action.type) {
  case ORDER_DELIVER_REQUEST:
   return { loading: true };

  case ORDER_DELIVER_SUCCESS:
   return { loading: false, success: true };

  case ORDER_DELIVER_FAIL:
   return { loading: false, error: action.payload };

  default:
   return state;
 }
};

好習慣,在 store 註冊完此 reducer 後,都要去前台查看 Redux DevTools 檢查是否有成功註冊

# actions

// orderActions.js
// 管理員更新訂單運送狀態為 已運送
export const deliverOrder = (order) => async (dispatch, getState) => {
 try {
  dispatch({
   type: ORDER_DELIVER_REQUEST,
  });

  const {
   userLogin: { userInfo },
  } = getState();

  const config = {
   headers: {
    "Content-type": "application/json",
    Authorization: `Bearer ${userInfo.token}`,
   },
  };

  const { data } = await axios.put(`/api/orders/${order.id}/delivered/`, {}, config);

  dispatch({
   type: ORDER_DELIVER_SUCCESS,
   payload: data,
  });
 } catch (error) {
  dispatch({
   type: ORDER_DELIVER_FAIL,
   payload: error.response && error.response.data.detail ? error.response.data.detail : error.message,
  });
 }
};

# 回到訂單明細前端頁面

更新運送狀態按鈕規劃放在訂單明細頁面,所以回到 OrderPage.js,加入觸發 deliverHandler 事件的按鈕

  • 當按下按鈕時,就在 usEffect 中重置 orderDeliver 的狀態
  • 要判斷只有 Admin、且訂單狀態為 已付款未運送 才顯示此按鈕
  • 為此按鈕添加 loading 狀態
// OrderListPage.js
const orderDeliver = useSelector((state) => state.orderDeliver);
const { loading: loadingDeliver, success: successDeliver } = orderDeliver;

const { userInfo } = useSelector((state) => state.userLogin);
...
useEffect(() => {
 if (!order || successPay || order.id !== Number(orderId) || successDeliver) {
  dispatch({ type: ORDER_PAY_RESET }); // 沒 RESET 會一直導致跑 <Loader />
  dispatch({ type: ORDER_DELIVER_RESET });

  dispatch(getOrderDetails(orderId));
 } else if (!order.isPaid) {
  setSdkReady(true);
 }
}, [dispatch, navigate, order, orderId, successPay, successDeliver]);

...
const deliverOrderHandler = () => {
 dispatch(deliverOrder(order));
};

return (
<>
...
 {/* 更新訂單運送狀態 按鈕 */}
 {loadingDeliver && <Loader />}
 {userInfo && userInfo.isAdmin && order.isPaid && !order.isDelivered && (
  <ListGroup.Item>
   <Button type='button' className='btn btn-block' onClick={deliverOrderHandler}>
    更新狀態為已送達
   </Button>
  </ListGroup.Item>
 )}
);
</>

最後一樣至 App.js 加入路由並用 <AdminRoute> 包裹以限制非管理員無法進入此頁面

最後成品畫面如下:

orderlistpage_done

# 解決無法訪問其他用戶問題

解決非用戶本人,連管理員也沒有權限訪問特定訂單的問題,比如說有另一個用戶的訂單,當初設計時,只有用戶本人可以查看自己的訂單,此邏輯是無誤的

other_user_order

但現在導致管理員也無法查看特定用戶的訂單,就有點不合理,所以這邊回到 order_views.py,在 if order.user == user: 的判斷中加入 or user.is_staff: 的判斷,管理員就能成功查看所有用戶的訂單了

# order_views.py
# 取得單一訂單
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def getOrderById(request, pk):
    user = request.user
    try:
        order = Order.objects.get(id=pk)
        if order.user == user or user.is_staff:
            serializer = OrderSerializer(order, many=False)
            return Response(serializer.data)
        else:
            return Response({'detail':'你沒有權限查看此訂單'},status=status.HTTP_400_BAD_REQUEST)
    except:
        return Response({'detail':'訂單不存在'},status=status.HTTP_400_BAD_REQUEST)