Agile育成ブログ
未来を変える喜びを
Django

書籍検索システム


Warning: count(): Parameter must be an array or an object that implements Countable in /home/xs638785/agile-software.site/public_html/wp-content/plugins/rich-table-of-content/functions.php on line 490

API

https://www.agile-software.site/2021/04/29/api/

トップページのURLを作成

views.pyのIndexViewに渡します。

from django.urls import path
from app import views

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]

書籍検索フォームの作成

titleを入力できるフォームを作成します。

from django import forms

class SearchForm(forms.Form):
    title = forms.CharField(label='タイトル', max_length=200, required=True)

viewの作成

フォームの内容を取得し、index.htmlテンプレートに渡すことになります。

https://www.agile-software.site/2021/04/24/django-render%e3%83%a1%e3%82%bd%e3%83%83%e3%83%89/
form = SearchForm(request.POST or None)
from django.views.generic import View
from django.shortcuts import render
from .forms import SearchForm


class IndexView(View):
    def get(self, request, *args, **kwargs):
        form = SearchForm(request.POST or None)

        return render(request, 'app/index.html', {
            'form': form
        })

base.html

もしユーザーがログイン状態であったらプロフィールとログアウトを、未ログインであればサインアップボタンとログインを表示する

                {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'profile' %}">プロフィール</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'account_logout' %}">ログアウト</a>
                    </li>
                {% else %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'account_signup' %}">サインアップ</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'account_login' %}">ログイン</a>
                    </li>
                {% endif %}

javaScript

    {% block extra_js %}
    {% endblock %}
{% load static %}

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <title>楽天ブックス書籍APIチュートリアル</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">楽天ブックス書籍API</a>
            <ul class="navbar-nav ml-auto">
                <li class="nav-item">
                    <a class="nav-link" href="/">ホーム</a>
                </li>
                {% if user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'profile' %}">プロフィール</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'account_logout' %}">ログアウト</a>
                    </li>
                {% else %}

                {% endif %}
            </ul>
        </div>
    </nav>

    <main>
        <div class="container">
            {% block content %}
            {% endblock %}
        </div>   
    </main>

    <footer class="py-2 bg-dark">
        <p class="m-0 text-center text-white">Copyright © Django Startup 2020</p>
    </footer>

    {% block extra_js %}
    {% endblock %}
</body>

</html>

index.html

widget_tweaksを使用することで、入力欄にテキストを用意することができます。

{% extends "app/base.html" %}
{% load widget_tweaks %}

{% block content %}

<div class="text-center my-5">
    <form method="post">
        {% csrf_token %}
        <div class="align-items-center">
            {% render_field form.title class="form-control" placeholder="キーワードから探す" %}
            <button class="btn btn-primary mt-2" type="submit">検索</button>    
        </div>
    </form>
</div>

{% endblock %}

検索ボタンをクリックして書籍のリストを表示させる

jsonをインポートします。jsonについては下記記事をご覧ください。

https://www.agile-software.site/2021/03/31/%e3%83%a2%e3%82%b8%e3%83%a5%e3%83%bc%e3%83%ab/

requests.getを使用することで、APIから情報を取得することができます。

api = requests.get(SEARCH_URL, params=params).text

form.cleaned_dataでフォームに入力したデータを取得する

パラメータで設定したデータを表示します。keyword変数には入力した文字列が入っています。
hit数を28にしているので画面には28個の書籍が表示されます。

keyword = form.cleaned_data['title']
from django.views.generic import View
from django.shortcuts import render
import json
import requests
from .forms import SearchForm

SEARCH_URL = 'https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&applicationId=1111111' # ID変更


def get_api_data(params):
    api = requests.get(SEARCH_URL, params=params).text
    result = json.loads(api)
    items = result['Items']
    return items


class IndexView(View):
    def get(self, request, *args, **kwargs):
        form = SearchForm(request.POST or None)

        return render(request, 'app/index.html', {
            'form': form
        })

    def post(self, request, *args, **kwargs):
        form = SearchForm(request.POST or None)

        if form.is_valid():
            keyword = form.cleaned_data['title']
            params = {
                'title': keyword,
                'hits': 28,
            }
            items = get_api_data(params)
            book_data = []
            for i in items:
                item = i['Item']
                title = item['title']
                image = item['largeImageUrl']
                isbn = item['isbn']
                query = {
                    'title': title,
                    'image': image,
                    'isbn': isbn
                }
                book_data.append(query)

            return render(request, 'app/book.html', {
                'book_data': book_data,
                'keyword': keyword
            })

        return render(request, 'app/index.html', {
            'form': 'form'
        })

index.htmlでフォーム画面をIndexViewとbook.htmlで検索結果の画面を表示させています。

index.html
book.html

book.html

ビューから渡されたデータをループで回して表示させます。
{{ book.title }}本のタイトルを太く表示させています。
<a class=”stretched-link” href=”{% url ‘detail’ book.isbn %}”></a>書籍をクリックしたら、詳細画面に遷移するようにリンクを設定します。

{% extends "app/base.html" %}

{% block content %}

<div class="text-center my-5">
    <div class="mb-5">
        <h3>「{{ keyword }}」の検索結果</h3>
    </div>
    <div class="row">
        {% for book in book_data %}
            <div class="col-3 mb-3">
                <div class="card img-thumbnail booklist h-100">
                    <img class="card-img-top card-thum" src="{{ book.image }}" alt="">
                    <div class="card-body text-center px-2 py-3">
                        <h5 class="font-weight-bold">{{ book.title }}</h5>
                    </div>
                    <a class="stretched-link" href=""></a>
                </div>
            </div>
        {% empty %}
            <p>該当するものはありません</p>
        {% endfor %}
    </div>
</div>

{% endblock %}

書籍の詳細を表示させます。

from django.urls import path
from app import views

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('detail/<str:isbn>', views.DetailView.as_view(), name='detail'), # 追加
]

パラメータを引数としてAPIを実行します。表示に必要なデータを取得して、各変数に格納していきます

class DetailView(View):
    def get(self, request, *args, **kwargs):
        isbn = self.kwargs['isbn']
        params = {
            'isbn': isbn
        }

        items = get_api_data(params)
        items = items[0]
        item = items['Item']
        title = item['title']
        image = item['largeImageUrl']
        author = item['author']
        itemPrice = item['itemPrice']
        salesDate = item['salesDate']
        publisherName = item['publisherName']
        size = item['size']
        isbn = item['isbn']
        itemCaption = item['itemCaption']
        itemUrl = item['itemUrl']
        reviewAverage = item['reviewAverage']
        reviewCount = item['reviewCount']

        book_data = {
            'title': title,
            'image': image,
            'author': author,
            'itemPrice': itemPrice,
            'salesDate': salesDate,
            'publisherName': publisherName,
            'size': size,
            'isbn': isbn,
            'itemCaption': itemCaption,
            'itemUrl': itemUrl,
            'reviewAverage': reviewAverage,
            'reviewCount': reviewCount,
            'average': float(reviewAverage) * 20,
        }

        return render(request, 'app/detail.html', {
            'book_data': book_data
        })

DetailViewとdetail.htmlで詳細画面を表示させています。

isbnとは書籍を世界共通で特定するための番号で、現在、ISBNは計13桁の番号で構成されています。
点数がパーセンテージでビューからもらえるので、widthをパーセンテージで指定すると、視覚的に分かります。

{% extends "app/base.html" %}

{% block content %}

<div class="my-5">
    <div class="row">
        <div class="col-md-6 text-center">
            <img src="{{ book_data.image }}" class="img-fluid" alt="">
        </div>

        <div class="col-md-6">
            <div class="card">
                <div class="card-body px-2 py-1">
                    <div class="p-4">
                        <h3>{{ book_data.title }}</h3>
                        <p class="mb-3">{{ book_data.author }}</p>
                        <hr>
                        {% if not book_data.reviewCount == 0 %}
                        <div class="d-flex flex-row">
                            <div class="star-rating">
                                <div class="star-rating-front text-warning" style="width: {{ book_data.average }}%">★★★★★</div>
                                <div class="star-rating-back">★★★★★</div>
                            </div>
                            <div class="average ml-2 text-danger">
                                {{ book_data.reviewAverage|floatformat:2 }}
                            </div>
                            <div class="d-flex align-items-center ml-2">
                                {{ book_data.reviewCount }}件
                            </div>
                        </div>
                        {% else %}
                            <p>まだレビューはありません。</p>
                        {% endif %}
                        <p class="mt-3">価格:<span class="text-danger font-weight-bold h3">{{ book_data.itemPrice }}円</span></p>
                        <a class="btn btn-primary" href="{{ book_data.itemUrl }}">買い物かごに入れる</a>
                    </div>
                </div>
            </div>
        </div>
    </div>

    {% if book_data.itemCaption %}
        <div class="mb-5">
            <h5>商品説明</h5>
            <hr>
            <p>{{ book_data.itemCaption }}</p>    
        </div>
    {% endif %}

    <div class="mb-5">
        <h5>商品情報</h5>
        <hr>
        <p>発売日:{{ book_data.salesDate }}</p>
        <p>著者/編集:{{ book_data.author }}</p>
        <p>出版社:{{ book_data.publisherName }}</p>
        <p>発行形態:{{ book_data.size }}</p>
        <p>ISBNコード:{{ book_data.isbn }}</p>
    </div>
</div>

{% endblock %}

CSS

/* detail */

.star-rating {
	position: relative;
	font-size: 20px;
	word-wrap: normal !important;
}

.star-rating-front {
	position: absolute;
	top: 0;
	left: 0;
	overflow: hidden;
}

.star-rating-back {
	color: #ccc;
}

.average {
	font-size: 20px;
	font-weight: bold;
}

You cannot copy content of this page