学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Django3.2 | 37 | QRオーダーシステムの構築 | 受注データ実装

[36 | 支払い処理実装] << [ホーム] >> [38 | 受注表示]

「qrmenu_server」側で作業します。


「qrmenu_server/qrmenucore/serializers.py」ファイルに記述を追加します。


記述追加 【Desktop/QRMenu/qrmenu_server/qrmenucore/serializers.py】28行目〜

from rest_framework import serializers
from . import models

class MenuItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.MenuItem
        fields = ( 'id',  'name', 'description', 'price', 'image', 'is_available', 'place', 'category')

class CategorySerializer(serializers.ModelSerializer):
    menu_items = MenuItemSerializer(many=True, read_only=True)
    
    class Meta:
        model = models.Category
        fields = ( 'id',  'name', 'menu_items', 'place')

class PlaceDetailSerializer(serializers.ModelSerializer):
    categories = CategorySerializer(many=True, read_only=True)
    class Meta:
        
        model = models.Place
        fields = ( 'id',  'name', 'image', 'number_of_tables', 'categories')

class PlaceSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Place
        fields = ('id', 'name', 'image')

class OrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Order
        fields = "__all__"
        



「qrmenucore/views.py」ファイルを編集します。


記述編集 【Desktop/QRMenu/qrmenu_server/qrmenucore/views.py】

import json
import stripe

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings

from rest_framework import generics
from . import models, serializers, permissions

# Create your views here.
class PlaceList(generics.ListCreateAPIView):
    serializer_class = serializers.PlaceSerializer

    def get_queryset(self):
        return models.Place.objects.filter(owner_id=self.request.user.id)
    
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

class PlaceDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [permissions.IsOwnerOrReadOnly]
    serializer_class = serializers.PlaceDetailSerializer
    queryset = models.Place.objects.all()

class CategoryList(generics.CreateAPIView):
     permission_classes = [permissions.PlaceOwnerOrReadOnly]
     serializer_class = serializers.CategorySerializer

class CategoryDetail(generics.UpdateAPIView, generics.DestroyAPIView):
    permission_classes = [permissions.PlaceOwnerOrReadOnly]
    serializer_class = serializers.CategorySerializer
    queryset = models.Category.objects.all()

class MenuItemList(generics.CreateAPIView):
     permission_classes = [permissions.PlaceOwnerOrReadOnly]
     serializer_class = serializers.MenuItemSerializer

class MenuItemDetail(generics.UpdateAPIView, generics.DestroyAPIView):
    permission_classes = [permissions.PlaceOwnerOrReadOnly]
    serializer_class = serializers.MenuItemSerializer
    queryset = models.MenuItem.objects.all()

stripe.api_key = settings.STRIPE_API_SECRET_KEY

@csrf_exempt
def create_pyment_intent(request):
   try:
        data = json.loads(request.body)
        intent = stripe.PaymentIntent.create(
            amount = data['amount'],
            currency = 'jpy',
            payment_method = data['payment_method']['id'],
            off_session=True,
            confirm = True,
        )

        order = models.Order.objects.create(
            place_id = data['place'],
            table = data['table'],
            detail = json.dumps(data['detail']),
            amount = data['amount'],
            payment_intent = intent['id']
        )

        return JsonResponse({
            "success": True,
            "order": order.id,
        })

   except Exception as e:
        return JsonResponse({
            "success": False,
            "error": str(e),
        })

class OrderList(generics.ListAPIView):
    serializer_class = serializers.OrderSerializer

    def get_queryset(self):
        return models.Order.objects.filter(place__owner_id=self.request.user.id, place_id=self.request.GET.get('place'))

class OrderDetail(generics.UpdateAPIView):
    permission_classes = [permissions.PlaceOwnerOrReadOnly]
    serializer_class = serializers.OrderSerializer
    queryset = models.Order.objects.all()    



パスの設定をします。


「qrmenu_server/urls.py」ファイルに記述を追加します。


記述追加 【QRMenu/qrmenu_server/qrmenu_server/urls.py】23行目

from django.contrib import admin
from django.urls import path, include

from qrmenucore import views

urlpatterns = [
    path('admin/', admin.site.urls),

    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.authtoken')),

    path('api/places/', views.PlaceList.as_view()),
    path('api/places/<pk>', views.PlaceDetail.as_view()),

    path('api/categories/', views.CategoryList.as_view()),
    path('api/categories/<pk>', views.CategoryDetail.as_view()),

    path('api/menu_items/', views.MenuItemList.as_view()),
    path('api/menu_items/<pk>', views.MenuItemDetail.as_view()),

    path('api/create_payment_intent/', views.create_pyment_intent),

    path('api/orders/', views.OrderList.as_view()),
    path('api/orders/<pk>', views.OrderDetail.as_view()),
]



次は「qrmenu_react」側で作業します。


「qrmenu_react/src/apis.js」ファイルに記述を追加します。


記述追加 【Desktop/QRMenu/qrmenu_react/src/apis.js】119行目

import { toast } from 'react-toastify';

function request(path,  {data = null, token = null, method = "GET" }) {
  return fetch(path, {
    method,
    headers: {
      Authorization: token ? `Token ${token}` : "",
      "Content-Type": "application/json",
    },
    body: method !=="GET" && method !== "DELETE" ? JSON.stringify(data): null,
  })
    .then((response) => {

      //もし成功したら
      if (response.ok) {
        if(method === "DELETE") {
          return true;
        }
        //toast.success("ログイン成功");
        return response.json();
      }
      //失敗
      return response.json().then((json) => {
          //JSONエラー
          if (response.status === 400) {
            //toast.error("氏名もしくはパスワードに間違いがあります。");
            const errors = Object.keys(json).map(
                (k) => `${(json[k].join(" "))}`
            );
            throw new Error(errors.join(" "));
          }
          throw new Error(JSON.stringify(json));
        })
        .catch((e) => {
          if (e.name === "SyntaxError") {
            throw new Error(response.statusText);
          }
          throw new Error(e);
        })
    })

    .catch((e) => {
      //全エラー
      toast(e.message, { type: "error" });
    })
}

export function signIn(username, password) {
  return request("/auth/token/login/", {
    data: {username, password},
    method: "POST",
  })
}

export function register(username, password) {
  return request("/auth/users/", {
    data: {username, password},
    method: "POST",
  })
}

export function fetchPlaces(token) {
  return request("/api/places/", {token});
}

export function addPlace(data, token) {
  return request("/api/places/", {data, token, method: "POST" });
}

export function uploadImage(image) {
  const formData = new FormData();
  formData.append("file", image);
  formData.append("upload_preset", "qrmenu_photos");

  return fetch("https://api.cloudinary.com/v1_1/dov57gocw/image/upload", {
    method: "POST",
    body: formData,
  }).then((response) => {
    return response.json();
  });
}

export function fetchPlace(id, token) {
  return request(`/api/places/${id}`, { token });
}

export function addCategory(data, token) {
  return request("/api/categories/", {data, token, method: "POST"});
}

export function addMenuItems(data, token) {
  return request("/api/menu_items/", {data, token, method: "POST"});
}

export function updateMenuItem(id, data, token) {
  return request(`/api/menu_items/${id}`, { data, token, method: "PATCH"});
}

export function removePlace(id, token) {
  return request(`/api/places/${id}`, {token, method: "DELETE"});
}

export function removeCategory(id, token) {
  return request(`/api/categories/${id}`, {token, method: "DELETE"});
}

export function removeMenuItem(id, token) {
  return request(`/api/menu_items/${id}`, {token, method: "DELETE"});
}

export function updatePlace(id, data, token) {
  return request(`/api/places/${id}`, { data, token, method: "PATCH"})
}

export function createPaymentIntent(data, token) {
  return request("/api/create_payment_intent/", {data, token, method: "POST"});
}

export function fetchOrders(placeId, token) {
  return request(`/api/orders/?place=${placeId}`, { token });
}



「qrmenu_react/src/pages」フォルダに「Orders.js」ファイルを新規作成します。
作成した「src/pages/Orders.js」ファイルを以下のように編集します。



新規作成 【Desktop/QRMenu/qrmenu_react/src/pages/Orders.js】

import { IoMdArrowBack } from 'react-icons/io';
import { Row, Col, Button } from 'react-bootstrap';
import { useParams, useHistory } from 'react-router-dom';
import React, { useState, useEffect, useContext } from 'react';

import { fetchOrders } from '../apis';
import AuthContext from '../contexts/AuthContext';
import MainLayout from '../layouts/MainLayout';

const Orders = () => {
    const [orders, setOrders] = useState([]);
    const params = useParams();
    const history = useHistory();
    const auth = useContext(AuthContext);

    const onBack = () => history.push(`/places/${params.id}`);

    const onFetchOrders = async () => {
        const json = await fetchOrders(params.id, auth.token);
        if(json) {
            console.log(json);
            setOrders(json);
        }
    }
    useEffect(() => {
        onFetchOrders();
    }, []);

    return (
        <MainLayout>
            <div className="d-flex align-items-center mb-4">
                <Button variant="link" onClick={onBack}>
                    <IoMdArrowBack size={25} color="black" />
                </Button>
                <h3 className="mb-0 ml-2 mr-2">受注リスト</h3>
            </div>

            <Row className="justify-content-center">
                {orders?.map((order) => (
                    <Col key={order.id} lg={8}>
                        {order.detail}
                    </Col>
                ))}
            </Row>
        </MainLayout>
    );
}

export default Orders;



「src/router/App.js」ファイルを編集します。


記述編集 【Desktop/QRMenu/qrmenu_react/src/router/App.js】

import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import React from 'react';

import { AuthProvider } from '../contexts/AuthContext';
import PrivateRoute from './PrivateRoute';

import Home from '../pages/Home';
import Login from '../pages/Login';
import Register from '../pages/Register';
import Places from '../pages/Places';
import Place from '../pages/Place';
import Menu from '../pages/Menu';
import Orders from '../pages/Orders';

function App() {
  return (
    <AuthProvider>
    <BrowserRouter>
      <Switch>
        <Route exact path='/'>
          <Home />
        </Route>
        <Route exact path='/login'>
          <Login />
        </Route>
        <Route exact path='/menu/:id/:table'>
          <Menu />
        </Route>
        <Route exact path='/register'>
          <Register />
        </Route>
        
        <PrivateRoute exact path='/places/:id'>
          <Place />
        </PrivateRoute>                  
        <PrivateRoute exact path='/places'>
          <Places />
        </PrivateRoute>
        <PrivateRoute exact path='/places/:id/orders'>
          <Orders />
        </PrivateRoute>            
      </Switch>
    </BrowserRouter>
    <ToastContainer />
    </AuthProvider>
  )
}

export default App;



ブラウザを確認します。
アドレスの数字には、注文があるplace_idを入れてください。
http://localhost:3000/places/5/orders


注文のdetailが表示されます。

注文のDetail表示
注文のDetail表示



↓↓クリックして頂けると励みになります。


[36 | 支払い処理実装] << [ホーム] >> [38 | 受注表示]