事象
Djangoで作ったWebアプリケーションをHerokuにデプロイしたら、modelの日本語文字列ソートが50音順になりませんでした。
modelの定義
sample
というDjangoアプリに、Item
というModelを定義し、ordering = ('name')
を指定することで、name
で昇順ソートしています。
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音順で表示されていましたが・・・
本番環境(Heroku)
本番環境(Heroku)のDjango管理画面では、さが、あいち、かながわ、と50音順とは異なる順で表示されてしまいました。
原因はデータベースの違い
開発環境ではデータベースに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にログイン
$ 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
カラムは
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音順になります。