【Django】セッション機能を使って検索条件を保持する方法

# Djangoのセッション管理

Djangoの `request.session` は、ユーザー固有のセッションデータを辞書のように扱い、セッションを介して状態情報を保持するための仕組みです。これを使うことで、ユーザーのブラウザ間で状態を維持し、複数のリクエストをまたいでデータを保持できます。以下は週刊少年ジャンプのワンピースを例に使ってます。

## セッションデータの保存と取得方法

### 保存方法
セッションデータは、キーと値のペアとして `request.session` に保存します。たとえば、検索条件などをセッションに保存する場合は以下のように書きます。

```python
self.request.session['wanted_search_data'] = self.request.GET.copy()
```

これは、ユーザーからの GET リクエストに含まれる検索条件をそのままセッションにコピーして保存する例です。

### 取り出し方法
セッションからデータを取り出すには、指定したキーを使います。

```python
wanted_search_data = self.request.session.get('wanted_search_data', None)
```

このコードは、セッションに保存された検索データを取得し、存在しない場合は `None` を返します。



## クラスベースビューのカスタマイズ例

以下では、賞金首（Wanted Posters）の一覧を管理する `WantedPosterListView` を例に、セッションを活用した検索条件の保持方法を示します。

### ビューの定義

```python
class WantedPosterListView(ListView):
    template_name = 'wantedposters_list.html'
    model = WantedPosters
    context_object_name = 'wanted_posters'
    paginate_by = 20
```
- **ページネーション (`paginate_by`)**  
  1ページあたり20件の賞金首を表示します。

### GETリクエストのハンドリング

```python
def get(self, request, *args, **kwargs):
    if not request.GET:
        # セッションから検索データを取得
        wanted_search_data = request.session.get('wanted_search_data', None)
        if wanted_search_data:
            # セッションにデータがある場合は、そのデータを使ってリダイレクト
            query = f"?{urlencode(wanted_search_data)}"
            url = reverse('wantedposter_list') + query
            return HttpResponseRedirect(url)
        else:
            # セッションにデータがない場合はデフォルトパラメータを設定
            query = "?pirate_name=&crew_name=&devil_fruit=&period_before=on&period_during=on&period_undefined=on"
            url = reverse('wantedposter_list') + query
            return HttpResponseRedirect(url)
    return super().get(request, *args, **kwargs)
```

- **検索パラメータが空の場合**  
  セッションに保存された検索条件があればそれをクエリとして付け、なければデフォルトの検索条件を使ってリダイレクトしています。

### クエリセットのカスタマイズ

```python
def get_queryset(self):
    form = WantedSearchForm(self.request.GET or None)
    if form.is_valid():
        # 検索条件をセッションに保存
        self.request.session['wanted_search_data'] = self.request.GET.copy()
    else:
        # フォームが不正または無い場合、セッションのデータをフォームに復元
        initial_data = self.request.session.get('wanted_search_data', {})
        form = WantedSearchForm(initial_data)

    queryset = WantedPosters.objects.all().order_by(*self.ordering)
    today = timezone.now().date()

    if form.is_valid():
        filters = form.cleaned_data
        # 名前や所属クルーなどのテキスト検索
        if filters.get('pirate_name'):
            queryset = queryset.filter(name__icontains=filters['pirate_name'])
        if filters.get('crew_name'):
            queryset = queryset.filter(crew__name__icontains=filters['crew_name'])
        if filters.get('devil_fruit'):
            queryset = queryset.filter(devil_fruit__icontains=filters['devil_fruit'])

        # 日付に基づくフィルタリング
        query = Q()
        if filters.get('period_before'):
            query |= Q(issue_date__gt=today)
        if filters.get('period_during'):
            query |= Q(issue_date__lte=today, expiry_date__gte=today)
        if filters.get('period_after'):
            query |= Q(expiry_date__lt=today)
        if filters.get('period_undefined'):
            query |= Q(issue_date__isnull=True) | Q(expiry_date__isnull=True)

        queryset = queryset.filter(query)

    return queryset.distinct()
```

- **検索フォーム (`WantedSearchForm`)**  
  ユーザーが入力した検索条件に基づき、賞金首データをフィルタリングします。  
- **セッションとの連携**  
  検索条件をセッションに保存し、フォーム再生成の際に復元することで、ページ遷移しても条件を維持します。

### コンテキストデータの追加

```python
def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    initial_data = self.request.session.get('wanted_search_data', {
        'period_before': True,
        'period_during': True,
        'period_undefined': True,
        'period_after': False
    })
    context['form'] = WantedSearchForm(initial=initial_data)
    wanted_posters = context['wanted_posters']

    # 発行日や期限日をフォーマット
    for poster in wanted_posters:
        poster.formatted_issue_date = poster.issue_date.strftime('%Y/%-m/%-d') if poster.issue_date else None
        poster.formatted_expiry_date = poster.expiry_date.strftime('%Y/%-m/%-d') if poster.expiry_date else None

    return context
```

- **フォームに初期値をセット**  
  セッションに保存された検索条件を読み込み、フォームの初期値として設定します。  
- **日付の表示形式を変更**  
  テンプレートで扱いやすいように、発行日や期限日をフォーマットして渡しています。



## まとめ

- Djangoのセッション機能を使うと、ユーザーが入力した検索条件などを継続的に保持できます。  
- クラスベースビューでは、`get_queryset` でフォームの入力内容をセッションに保存し、`get` メソッドや `get_context_data` で適宜セッションデータを利用する構成が便利です。  
- 賞金首一覧の例のように、フィルタ条件を継続的に保持しながらページングや再検索を行う場面で役立ちます。