【Django】ページをPDF出力する(障害対応)【Python】

# 初めに
※以下の記事の続きです。未読の場合は先に目を通してください。
[【Django】ページをPDF出力する(セッション)【Python】](https://qiita.com/nagisa_O/items/9cdd82b8313a0fbb0400)

前回、セッションを利用し自身とpdfkitで値の受け渡しを行う処理を実装しました。
一見処理は上手くいっているように見えましたが、pdfkitがセッションから取り出す値が一周遅れていることに気付きましたので、原因の検証と対策をまとめます。

# 環境
|名称  |バージョン  |
|---|---|
|Python  |3.10.4  |
|Django  |4.0.6 |
|pdfkit  |1.0.0 |

# 障害内容
前回まとめた、以下の「やりたいこと」をベースに障害の内容を確認します。

やりたいこと
---------------------------フロントエンド-------------------------
1.ユーザ操作で出力のボタンが押される
2.選択された対象のPK(今回はID)をバックエンドに渡す
---------------------------バックエンド---------------------------
3.セッションにPKをセットする
4.pdfkitを呼び出す(この際にセッションIDを渡す)
5.pdfkitがセッションからPKを取り出し、PKをキーにDBアクセスする
6.DBから取得した値をフロントに戻す
---------------------------フロントエンド-------------------------
7.1件だけ表示されたページをpdf出力する

1回目のユーザ操作で、1番目の行を出力した場合、
上記の「3.セッションにPKをセットする」でセッションにIDの「1」をセットします。
そして「5.pdfkitがセッションからPKを取り出し...」でセッションの値を取得しますが、
取り出せるのは先ほどセットした「1」ではなく、「None」です。

2回目のユーザ操作で、2番目の行を出力した場合は、
「3.セッションにPKをセットする」でセッションにIDの「2」をセットしますが、
「5.pdfkitがセッションからPKを取り出し...」で取得できるのは、1回目にセットした「1」になってしまいます。

このように、一連の流れを一周とした場合、ユーザがセッションに格納する値をpdfkitは一周遅れて取得してしまうのです。
確認の為、pdf出力関数とpdfkitがルーティングされている関数にセッションを出力する処理を挟み、上記の動きを再現します。

```php:views.py
def to_pdf_alone(request):
    # idを受け取る
    id = request.POST.get('id')

    # idをセッションにセットする
    request.session['id'] = id
    url = 'http://127.0.0.1:8000/test_app/detail/'
    cookie_list = request.COOKIES
    options = {
            'cookie': [
                ('sessionid', cookie_list['sessionid']),
            ]
        }
        
    print(f'セッションにセットした値: {id}')  #セッションの値を確認

    pdfkit.from_url(url, 'sample.pdf', options=options)
    return redirect('test_app:index')

def detail(request):
    # idをセッションから受け取る
    id = request.session.get('id')
    
    print(f'セッションから取り出した値: {id}')  #セッションの値を確認

    # idをキーにDBアクセス
    person = Person.objects.filter(id=id)

    context = {'item': {'id': person[0].id, 'first_name': person[0].first_name, 'last_name': person[0].last_name}}
    return render(request, 'test_app/detail.html', context)

```

1回目の出力結果は以下のようになりました。
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225075/ed4e655b-dea6-0005-a9c0-ae445ab2d8fd.jpeg)

2回目の出力結果は以下のようになりました。
![キャプチャ1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225075/30baa216-3c90-5b9e-af85-030df0d8ee28.jpeg)
pdfkitがセッションから取得する値が一周遅れていることが確認できました。

# 原因
原因はセッションが保存されるタイミングにあります。
DjangoはデフォルトでセッションはDBに保存されるようになっています。
テーブル名は「django_session」です。中身を確認してみましょう。
![キャプチャ2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225075/ce10a086-e569-6f35-7934-62acb23b1451.jpeg)

「session_key」はセッションIDです。
「session_data」がセッションに格納した値で、Base64でエンコードされています。
DBに値が格納されていることで、処理を逐一止めて都度都度DBを確認すればどのタイミングで更新されているかがわかります。

結論は関数「to_pdf_alone」の「return redirect('test_app:index')」時で、
indexにリダイレクトされた直後にDBを確認すると値が変わっているのが確認できます。
Djangoのセッションの値は、セッションがセットされた関数が「return」されたタイミングで更新されると考えられます。

# 対策
原因が判明すれば対策は簡単で、自身でセッションに値を格納後、セッションを保存すればよいです。
関数「to_pdf_alone」にセッションを保存する処理を追記しました。
```php:views.py
def to_pdf_alone(request):
    # idを受け取る
    id = request.POST.get('id')

    # idをセッションにセットする
    request.session['id'] = id

    # セッションを保存する
    request.session.save()

    url = 'http://127.0.0.1:8000/test_app/detail/'
    cookie_list = request.COOKIES
    options = {
            'cookie': [
                ('sessionid', cookie_list['sessionid']),
            ]
        }
        
    print(f'セッションにセットした値: {id}')  #セッションの値を確認

    pdfkit.from_url(url, 'sample.pdf', options=options)
    return redirect('test_app:index')

```
3行目をpdf出力して確認してみます。
![キャプチャ3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225075/5398692a-f60c-e65d-a098-9395b4d7b797.jpeg)

このことは公式ドキュメントにも記載されています。
[How to use sessions](https://docs.djangoproject.com/en/4.0/topics/http/sessions/)

# まとめ
前回で全て完了したかと思っていましたが、思わぬところで躓いてしまいました。
セッションは格納直後に更新されると思っていたので、良い勉強になりました。

# おまけ
Djangoはセッションエンジンを変更できますが、
「django.contrib.sessions.backends.cache」に変更した場合も動きは同じでした。
そりゃ保存タイミングは変わりませんか。