12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-18

事象

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

modelの定義

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

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音順で表示されていましたが・・・
あいち、かながわ、さがの順で表示されるスクリーンショット

本番環境(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カラムは

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カラムのCollationja_JP.utf8が指定されています。

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

参考

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?