Djangoのソートがあいうえお順にならない時の対処法

# 事象

Djangoで作ったWebアプリケーションをHerokuにデプロイしたら、modelの日本語文字列ソートが50音順になりませんでした。

## modelの定義
`sample`というDjangoアプリに、`Item`というModelを定義し、`ordering = ('name')`を指定することで、`name`で昇順ソートしています。

```python:sample/models.py
class Item(models.Model):
    name = models.CharField(verbose_name='名前', max_length=255)
    created_at = models.DateTimeField(verbose_name='作成日', auto_now_add=True)

    class Meta:
        ordering = ('name') # nameで昇順ソート

    def __str__(self):
        return self.name
```
## Djangoの管理画面での表示

Djangoの管理画面でこのmodelに格納されたデータを見てみます。
modelには

- かながわ
- あいち
- さが

というデータが入っています。

### 開発環境（ローカル）
開発環境のDjango管理画面では、**あ**いち、**か**ながわ、**さ**が、と50音順で表示されていましたが・・・
<img width="128" border="1" alt="あいち、かながわ、さがの順で表示されるスクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/317369/2d4a47c1-f5db-9920-c815-e1fe40467478.png">

### 本番環境(Heroku)
本番環境(Heroku)のDjango管理画面では、**さ**が、**あ**いち、**か**ながわ、と50音順とは異なる順で表示されてしまいました。
<img width="128" alt="さが、あいち、かながわの順で表示されるスクリーンショット" src="https://qiita-image-store.s3.amazonaws.com/0/317369/a0d7b3a8-4e87-fbc6-0780-727f43ea9e1f.png">

# 原因はデータベースの違い

開発環境ではデータベースにDjango標準のSQLiteを使用していましたが、本番環境のデータベースがPostgreSQLとなったためでした。

文字列のソート順にはCOLLATE（照合順序）の設定が関わってきますが、PostgreSQLは、COLLATEのデフォルト設定が`en_US.utf8`になっており、日本語文字列のソートが50音順にはならないようです。

# 対処方法その1 生のSQL文を書いて対処する

生のSQL文を使い、ORDER BY句では`ORDER BY name COLLATE “ja_JP.utf8”`のように指定すると日本語が50音順にソートされます。

生のSQLを使わずに、DjangoのQuerySetでCOLLATEを指定する方法は無いようです。

# 対処方法その2 テーブルのカラムのCOLLATEの定義を変更する

Djangoで生のSQL文を書くことを避けたい場合は、ソートに使うテーブルのカラムのCOLLATEの定義を変更します。以下、その手順を記載します。

## 1. Herokuにログイン
```bash
$ heroku login
heroku: Enter your login credentials
Email [hoge@gmail.com]: hoge@gmail.com # メールアドレスを入力
Password: ***********
Logged in as hoge@gmail.com
```

## 2. Herokuのbashを起動
Heroku上でコマンドを打つ為、`heroku run bash`で、bashを起動します。

```
$ heroku run bash
Running bash on ⬢ (Herokuのアプリ名)... up, run.5883 (Hobby)
```

## 3. Djangoのdbshellを起動
`manage.py`のあるディレクトリで、`python manage.py dbshell`を実行します。

```
~ $ python manage.py dbshell
/app/.heroku/python/lib/python3.6/site-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>.
  """)
psql (10.5 (Ubuntu 10.5-2.pgdg18.04+1), server 10.6 (Ubuntu 10.6-1.pgdg14.04+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

xxxxxxxxxxxxxx=>
```

## 4. COLLATEを変更したいテーブル名を確認する

`\dt`とコマンドを打つと、Djangoアプリで使用しているテーブル名の一覧が表示されます。

```
xxxxxxxxxxxxxx=>\dt
```

```
                      List of relations
 Schema |            Name            | Type  |     Owner      
--------+----------------------------+-------+----------------
 public | auth_group                 | table | jtierooaexjdbq
 public | auth_group_permissions     | table | jtierooaexjdbq
 public | auth_permission            | table | jtierooaexjdbq
 public | auth_user                  | table | jtierooaexjdbq
 public | auth_user_groups           | table | jtierooaexjdbq
 public | auth_user_user_permissions | table | jtierooaexjdbq
 public | django_admin_log           | table | jtierooaexjdbq
:
```

`enterキー`を押すと、表示しきれなかったテーブル名が1行づつ表示され、何度も押すとやがて`(END)`が表示されます。

```
 public | sample_item                | table | jtierooaexjdbq
(20 rows)

(END)
```
テーブル名が`sample_item`であることが確認できました。
ここから元のコマンドラインに戻る場合は、`q`を押します。

## 5. COLLATEを変更したいテーブルの詳細情報を確認する

`\d`に続けて、テーブル名を入力します。

```
xxxxxxxxxxxxxx=> \d sample_item
```

すると、そのテーブルの各カラムの定義が確認できます。

```
                                       Table "public.sample_item"
   Column    |           Type           | Collation | Nullable |                 Default                 
-------------+--------------------------+-----------+----------+-----------------------------------------
 id          | integer                  |           | not null | nextval('sample_item_id_seq'::regclass)
 name        | character varying(255)   |           | not null | 
 created_at  | timestamp with time zone |           | not null | 
Indexes:
    "sample_item_pkey" PRIMARY KEY, btree (id)
```

`name`カラムの`Collation`が何も指定されておらず、PostgreSQLのデフォルトのCOLLATEが適用されることがわかります。

## 6. PostgreSQLのデフォルトのCOLLATEを確認する

ついでに、データベースのデフォルトの`COLLATE`がどのように設定されているかも確認してみます。`SHOW LC_COLLATE;`と入力します。

```
xxxxxxxxxxxxxx=> SHOW LC_COLLATE;
 lc_collate  
-------------
 en_US.UTF-8
(1 row)
```

`en_US.UTF-8`という設定になっています。この為、日本語が50音順にソートされません。

## 7. 任意のテーブルの任意のカラムについて、COLLATEの定義を変更する

`ALTER TABLE （テーブル名) ALTER COLUMN （カラム名） TYPE (カラムの型) COLLATE "ja_JP.utf8";`と実行します。

注意点として、ALTER COLUMNでは、COLLATEの定義だけを変更することはできず、カラムの型も指定する必要があるようです。その為、カラムの型まで変更してしまうことの無いよう、もともとの定義をそのまま指定するようにしてください。

今回の例だと、`name`カラムは

```python:sample/models.py
class Item(models.Model):
    name = models.CharField(verbose_name='名前', max_length=255)
# （以下略）
```

と、モデルフィールドは`CharField`で、`max_length`は255となっていますので、さきほどのカラムの型は`VARCHAR(255)`にします。

```
xxxxxxxxxxxxxx=>ALTER TABLE sample_item ALTER COLUMN name TYPE VARCHAR(255) COLLATE "ja_JP.utf8";
ALTER TABLE
```
成功すると、`ALTER TABLE`と表示されます。

## 8. COLLATEを変更したテーブルの詳細情報を確認する

\dに続けて、テーブル名を入力します。

```
xxxxxxxxxxxxxx=> \d sample_item
```

```
                                       Table "public.sample_item"
   Column    |           Type           | Collation | Nullable |                 Default                 
-------------+--------------------------+-----------+----------+-----------------------------------------
 id          | integer                  |           | not null | nextval('sample_item_id_seq'::regclass)
 name        | character varying(255)   |ja_JP.utf8 | not null | 
 created_at  | timestamp with time zone |           | not null | 
Indexes:
    "sample_item_pkey" PRIMARY KEY, btree (id)
```

`name`カラムの`Collation`に`ja_JP.utf8`が指定されています。

これで、`name`を使って昇順ソートした場合に、50音順になります。

# 参考

- [PostgreSQLで正しく文字列ソート出来ない時の解決方法](https://qiita.com/anoworl/items/af9f12f915b8969ea40d)
- [[Django]querysetで照合順序(COLLATE)を指定するにはどうしたらいいの？](https://qiita.com/toriumi01/items/a196b80f878130dd9399)
- [Django モデルフィールド：データベースフィールド 型対応表](https://qiita.com/okoppe8/items/13ad7b7d52fd6a3fd3fc)