商談履歴管理システムの作成

この記事は、「Djangoを使用した顧客管理システムの作成」の4つ目の記事です。

https://qiita.com/tomo0227/items/cc893ea4e8e6cfb6ad77

前回顧客管理のCRUD機能を作りました。今回は、商談履歴のCRUD機能を作成していきます。
## 商談履歴のCRUD機能の実装
### 作業内容
* 商談履歴のCRUD
    * 商談履歴の登録・閲覧・編集・削除するビューとテンプレートの作成
    * 顧客ごとの商談履歴を一覧表示
### ビューの作成

https://qiita.com/tomo0227/items/054c974e104f81ea82db

↑前回の記事で作成したviews.pyを編集していきます。前回の記事ですでに商談履歴モデル```Deal```はインポート済みなので、クラスの作成から行っていきます。
```crm_app/views.py
from django.core.paginator import Paginator #追加
from .forms import AddDealForm #追加

#商談履歴の閲覧
class DealList(View):
    def get(self,request):
        sort_by = request.GET.get('sort', 'date')  # デフォルトは商談日でソート
        deal = Deal.objects.all().order_by(sort_by)
        paginator = Paginator(deal,10) #1ページに10件
        page = request.GET.get('page')
        deals = paginator.get_page(page)
        return render(request,'crm_app/deal_list.html',{'deal':deals}) 

#商談情報の追加
class AddDeal(View):
    def get(self,request):
        form = AddDealForm()
        return render(request,'crm_app/add_deal.html',{'form':form})

    def post(self,request):
        form = AddDealForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('deal_list')
        return render(request,'crm_app/add_deal.html',{'form':form})


#商談情報の編集
class EditDeal(View):
    def get(self,request,id):
        deal = get_object_or_404(Deal,id=id)
        form = AddDealForm(instance=deal)
        return render(request,'crm_app/edit_deal.html',{'form':form,'id':id})      
    
    def post(self,request,id):
        deal = get_object_or_404(Deal,id=id)
        form = AddDealForm(request.POST,instance=deal)
        if form.is_valid():
            form.save()
            return redirect('deal_list')
        return render(request,'crm_app/edit_deal.html',{'form':form,'id':id})

#商談情報の削除
class DeleteDeal(View):
    def get(self,request,id):
        deal = get_object_or_404(Deal,id=id)
        return render(request,'crm_app/delete_deal.html',{'deal':deal})
    
    def post(self,request,id):
        deal = get_object_or_404(Deal,id=id)
        deal.delete()
        return redirect('deal_list')
        
deal_list = DealList.as_view()
add_deal = AddDeal.as_view()
edit_deal = EditDeal.as_view()
delete_deal = DeleteDeal.as_view()
```
基本的に前回記事で作成したCRUD機能を同様に作っています。違う点として、商談履歴一覧表示にページネーションを付けてみました。ページネーションとは情報をページごとに分割して整理するものです。今回は、商談履歴が1ページに10件だけ表示されるようにし、10件を超える商談履歴が保存されたときは、複数ページ作られるようになっています。

### テンプレートの作成
こちらも前回同様に作成したビューを基に```deal_list.html```、```add_deal.html```、```edit_deal.html```、```delete_deal.html```を作成していきます。ベースとして前回作成した```base.html```を使用します。テンプレートは前回とほぼ同じなので、ページネーションを追加した```deal_list.html```のみ紹介します。
```templates/crm_app/deal_list.html
{% extends 'base.html' %}

{% block content %}
<div class="container mt-4">
    <h2>商談履歴一覧</h2>
    <a href="{% url 'add_deal' %}" class="btn btn-primary mb-3">新規商談登録</a>
    
    <div class="mb-3">
        <span class="me-2">ソート:</span>
        <a href="?sort=date" class="btn btn-outline-secondary btn-sm">商談日</a>
        <a href="?sort=customer" class="btn btn-outline-secondary btn-sm">顧客名</a>
        <a href="?sort=title" class="btn btn-outline-secondary btn-sm">商談タイトル</a>
        <a href="?sort=status" class="btn btn-outline-secondary btn-sm">ステータス</a>
    </div>
    
    <table class="table table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>顧客名</th>
                <th>商談日</th>
                <th>商談内容</th>
            </tr>
        </thead>
        <tbody>
            {% for d in deal %}
            <tr>
                <td>{{ d.id }}</td>
                <td>{{ d.customer }}</td>
                <td>{{ d.date|date:"Y/m/d" }}</td>
                <td>{{ d.content }}</td>
                <td>
                    <a href="{% url 'edit_deal' d.id %}" class="btn btn-sm btn-warning">編集</a>
                    <a href="{% url 'delete_deal' d.id %}" class="btn btn-sm btn-danger">削除</a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    #ページネーション
    {% if deal.has_other_pages %}
    <nav aria-label="Page navigation" class="mt-4">
        <ul class="pagination justify-content-center">
            {% if deal.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ deal.previous_page_number }}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">前へ</a>
                </li>
            {% else %}
                <li class="page-item disabled">
                    <span class="page-link">前へ</span>
                </li>
            {% endif %}

            {% for i in deal.paginator.page_range %}
                {% if deal.number == i %}
                    <li class="page-item active">
                        <span class="page-link">{{ i }}</span>
                    </li>
                {% else %}
                    <li class="page-item">
                        <a class="page-link" href="?page={{ i }}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">{{ i }}</a>
                    </li>
                {% endif %}
            {% endfor %}

            {% if deal.has_next %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ deal.next_page_number }}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">次へ</a>
                </li>
            {% else %}
                <li class="page-item disabled">
                    <span class="page-link">次へ</span>
                </li>
            {% endif %}
        </ul>
    </nav>
    {% endif %}
</div>
{% endblock %}
```
上記のプログラムの一覧表示の部分は前回とほぼ同じなので、ページネーション部分から説明していきます。
```{% if deal.has_other_pages %}```は、複数ページあるとき```True```を返します。つまり商談履歴が11件以上あるとき、このコードより下の部分が表示されるということです。

```{% if deal.has_previous %}```は、「前のページがあるか」を確認しています。前のページがあるとき```True```を返し、リンクを表示します。

```{% for i in deal.paginator.page_range %}```は、ページ数に応じて、ページ番号を作成しています。今いるページ以外のページ番号は、リンクにして、ページ移動ができるようにしています。

```{% if deal.has_next %}```は、「次のページがあるか」を確認しています。動作としては```{% if deal.has_previous %}```と同様です。

最後にリンクのURL部分```<a class="page-link" href="?page={{ i }}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">{{ i }}</a>```についてです。```?page={{ i }}```はURLにページ番号を渡している部分です。

```https://example.com/?page=2```のようなURLを見たことがあると思います。これと同じようにURLを渡しているということです。
```{% if request.GET.sort %}```の部分は現在のページはソート(並び方の指定)がされているかを確認しています。ページが移動してソートが変わるのは使い勝手が悪くなってしますので、ソート内容も引き継ぐために使用しています。

### フォームの作成とルーティング設定
ここも前回とほぼ同じなので、さらっと行きます。
```crm_app/forms.py```に```AddDealForm```クラスを作成します。
```python:crm_app/forms.py
class AddDealForm(forms.ModelForm):
    class Meta:
        model = Deal
        fields = ['customer','title','date','status','content','responsible_staff']
```
違いとしては、```field```ぐらいですね。モデルが違うので、```field```が違うのは当たり前です。

ルーティング設定は、プロジェクトのほうは前回設定したので、アプリケーションのほうだけ設定します。
```crm_app/urls.py
urlpatterns = [

    ...
    #追加
    path('deals/',deal_list,name='deal_list'),                 
    path('deals/add/',add_deal,name='add_deal'),               
    path('deals/<id>/edit/',edit_deal,name='edit_deal'), 
    path('deals/<id>/delete/',delete_deal,name='delete_deal'), 
]
```
ここはURLの名前が違うだけです。

今回はここまでにします。今までの部分だけでも普通にwebアプリとして成り立つと思います。
次回は検索機能とダッシュボード機能を追加していきます。

1つ目：Djangoを使用した顧客管理システムの作成
https://qiita.com/tomo0227/items/cc893ea4e8e6cfb6ad77
2つ目：顧客管理システムのモデル作成
https://qiita.com/tomo0227/items/11f6a262ee1da183fd70
3つ目：顧客管理システムのCRUD機能の実装
https://qiita.com/tomo0227/items/054c974e104f81ea82db
4つ目：商談履歴管理システムの作成
https://qiita.com/tomo0227/items/7b9934ab4bc36cdcd4e8
5つ目：Djangoで検索・ダッシュボード機能を作ってみた。
https://qiita.com/tomo0227/items/a86f7bf038e87be11892