【Django】エラー「AttributeError: module 'django.contrib.admin' has no attribute 'display'」の原因とは

# 概要
以前作成した以下チュートリアルを動かしていたら、`AttributeError: module 'django.contrib.admin' has no attribute 'display'`というエラーが出ました。
[はじめての Django アプリ作成、その 1](https://docs.djangoproject.com/ja/4.1/intro/tutorial01/)

本記事では、このエラーの解決方法を記載します。

### エラー内容

```
Traceback (most recent call last):
  # 中略
  File "/mysite/mysite/polls/models.py", line 7, in <module>
    class Question(models.Model):
  File "/mysite/mysite/polls/models.py", line 12, in Question
    @admin.display(boolean=True, ordering='pub_date', description='Published recently?')
AttributeError: module 'django.contrib.admin' has no attribute 'display'
```

使用しているdjangoのバージョンはDjango 3.0.5です。

## エラー原因
まず、エラーになったコードは以下`models.py`です。

```polls/models.py
import datetime

from django.db import models
from django.utils import timezone
from django.contrib import admin

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return self.question_text
    @admin.display(
        boolean=True,
        ordering='pub_date',
        description='Published recently?',
    )
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text
```

なぜ上記コードでエラーになったのか？
それは、Django 3.0.5では`@admin.display`デコレータは使用できないため。
`@admin.display`デコレータはDjango 3.2以降で導入されたらしい。

> admin用の新しいデコレータdisplay()・action()が追加されました。
引用元：[Django 3.2 LTS 主な変更点まとめ](https://ryu22e.org/posts/2021/04/06/django32-new-features/)

確かに、チュートリアルでのDjangoのバージョンは、以下のように、`4.1`と記載があります。

> This tutorial is written for Django 4.1,

今回、自分がDjangoのバージョンを3.0.5で`polls`アプリを立ち上げようとしたため、上述のエラーが発生したのです。

### `@admin.display`とは？
`@admin.display`デコレータとは、Djangoの管理サイト（Admin Site）でモデルのフィールドやメソッドをカスタマイズするために使用されるデコレータです。特定のフィールド名を管理者用に任意の名前に変更したりできます。

`@admin.display`を消す場合、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/f96cc4de-1b5b-ad43-2b18-938d59fe3792.png)

`@admin.display`がある場合、以下のようになります。
`'Published recently?'`の部分（任意に決めた部分）が変わっていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/d48957d9-6a2c-fe44-7bea-dba0a5b773ea.png)

djangoチュートリアルでは、`admin.py`と`tests.py`で詳細な設定をしているので、気になる方はそちらをご参照ください。

## 解決策

ということで、解決方法は二つ。
一つ目は、Djangoを`4.1`以降でインストールする。

二つ目は、3.0.5を使い続ける場合。
その場合は、`@admin.display`デコレータの代わりに`admin_order_field、boolean`、`short_description`属性を使用してカスタムの表示方法を指定します。


```polls/models.py
from django.db import models
from django.utils import timezone
from django.contrib import admin

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    
    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        now = timezone.now()
        return now - timezone.timedelta(days=1) <= self.pub_date <= now

    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    
    def __str__(self):
        return self.choice_text
```

ということで、原因はDjangoチュートリアルで推奨のバージョンを使用していなかった、ということでした。もしも3.2より前で使ってみたい場合は上記のように変えるとうまくいきます。