【Django】CBVの基本メソッド（get_queryset、get_context_data）


## はじめに

Djangoの汎用ビューを活用する際、`get_queryset`と`get_context_data`はデータ処理とテンプレートへのデータ渡しをカスタマイズするために非常に便利なメソッドです。本記事では、具体例を通してこれらのメソッドの使い方を解説します。



## `get_queryset` メソッド

### 概要
- **目的**: 表示するオブジェクトのクエリセットを定義します。
- **特徴**:
  - フィルタリングやソートのロジックをカスタマイズ可能。
  - デフォルトでは、モデルに関連付けられた全てのオブジェクトを返します。

### 使用例
ユーザーの検索条件に基づき、`Contracts`モデルからデータを取得して絞り込む実装を行います。

```python
    def get_queryset(self):
        """
        ユーザーの検索条件に基づいてクエリセットを取得する。
        """
        form = ItemSearchForm(self.request.GET or None)  # フォーム名をマスキング
        if form.is_valid():
            self.request.session['search_data'] = self.request.GET.copy()  # セッションに保存
        else:
            # セッションから検索条件を取得
            initial_data = self.request.session.get('search_data', {})
            form = ItemSearchForm(initial_data)

        queryset = Items.objects.all().order_by(*self.ordering)  # デフォルトクエリセット
        today = timezone.now().date()  # 今日の日付

        if form.is_valid():
            filters = form.cleaned_data
            if filters.get('keyword'):
                queryset = queryset.filter(name__icontains=filters['keyword'])
            if filters.get('category'):
                queryset = queryset.filter(category__name__icontains=filters['category'])
            if filters.get('status'):
                queryset = queryset.filter(status__icontains=filters['status'])

            # 日付フィルタリング
            date_query = Q()
            if filters.get('before'):
                date_query |= Q(start_date__gt=today)
            if filters.get('current'):
                date_query |= Q(start_date__lte=today, end_date__gte=today)
            if filters.get('undefined'):
                date_query |= Q(start_date__isnull=True) | Q(end_date__isnull=True)

            queryset = queryset.filter(date_query)

        return queryset.distinct()
```



## `get_context_data` メソッド

### 概要
- **目的**: テンプレートに渡すコンテキストデータを追加または変更します。
- **特徴**:
  - 親クラスの`get_context_data`で生成された基本データに追加情報を加える。
  - 動的なデータをテンプレートに渡す際に活用します。

### 使用例
以下は、検索フォームと契約データの開始日・終了日をテンプレートに渡す実装です。

```python
    def get_context_data(self, **kwargs):
        """
        テンプレートに渡す追加のコンテキストデータを生成。
        """
        context = super().get_context_data(**kwargs)
        initial_data = self.request.session.get('search_data', {
            'before': True,
            'current': True,
            'undefined': True
        })
        context['form'] = ItemSearchForm(initial=initial_data)
        items = context['items']

        # 日付のフォーマットを追加
        for item in items:
            item.formatted_start_date = (
                item.start_date.strftime('%Y/%m/%d') if item.start_date else None
            )
            item.formatted_end_date = (
                item.end_date.strftime('%Y/%m/%d') if item.end_date else None
            )

        return context
```



## 実装例全体

```python
class ItemListView(LoginRequiredMixin, OrderedListMixin, ListView):
    template_name = 'item_list.html'
    model = Items  # モデル名をマスキング
    context_object_name = 'items'  # テンプレート内での名前
    paginate_by = 20  # 1ページあたりの表示件数

    def get(self, request, *args, **kwargs):
        if not request.GET:
            search_data = request.session.get('search_data', None)  # セッションからデータ取得
            if search_data:
                query = f"?{urlencode(search_data)}"
                url = reverse('item_list') + query
                return HttpResponseRedirect(url)
            else:
                # デフォルト検索条件
                query = "?keyword=&category=&status=&before=on&current=on&undefined=on"
                url = reverse('item_list') + query
                return HttpResponseRedirect(url)
        return super().get(request, *args, **kwargs)

    def get_queryset(self):
        """
        ユーザーの検索条件に基づいてクエリセットを取得する。
        """
        form = ItemSearchForm(self.request.GET or None)  # フォーム名をマスキング
        if form.is_valid():
            self.request.session['search_data'] = self.request.GET.copy()  # セッションに保存
        else:
            # セッションから検索条件を取得
            initial_data = self.request.session.get('search_data', {})
            form = ItemSearchForm(initial_data)

        queryset = Items.objects.all().order_by(*self.ordering)  # デフォルトクエリセット
        today = timezone.now().date()  # 今日の日付

        if form.is_valid():
            filters = form.cleaned_data
            if filters.get('keyword'):
                queryset = queryset.filter(name__icontains=filters['keyword'])
            if filters.get('category'):
                queryset = queryset.filter(category__name__icontains=filters['category'])
            if filters.get('status'):
                queryset = queryset.filter(status__icontains=filters['status'])

            # 日付フィルタリング
            date_query = Q()
            if filters.get('before'):
                date_query |= Q(start_date__gt=today)
            if filters.get('current'):
                date_query |= Q(start_date__lte=today, end_date__gte=today)
            if filters.get('undefined'):
                date_query |= Q(start_date__isnull=True) | Q(end_date__isnull=True)

            queryset = queryset.filter(date_query)

        return queryset.distinct()

    def get_context_data(self, **kwargs):
        """
        テンプレートに渡す追加のコンテキストデータを生成。
        """
        context = super().get_context_data(**kwargs)
        initial_data = self.request.session.get('search_data', {
            'before': True,
            'current': True,
            'undefined': True
        })
        context['form'] = ItemSearchForm(initial=initial_data)
        items = context['items']

        # 日付のフォーマットを追加
        for item in items:
            item.formatted_start_date = (
                item.start_date.strftime('%Y/%m/%d') if item.start_date else None
            )
            item.formatted_end_date = (
                item.end_date.strftime('%Y/%m/%d') if item.end_date else None
            )

        return context
```


## まとめ

- **`get_queryset`**: ユーザーの検索条件に基づくデータの取得や絞り込みを行います。
- **`get_context_data`**: テンプレートで使用するための追加情報を提供します。

これらのメソッドを適切に活用することで、Djangoビューを柔軟にカスタマイズできます。