Django の MySQL 向けモデルで大文字と小文字を区別できるようにする

はじめに

 Django でデータベースバックエンドに MySQL を利用しているとき、CharField や TextField では大文字と小文字を区別しません。例えば「Value A」と「Value a」を区別して取得する場合、

result = Item.objects.filter(value='Value A')
のように取得すると、「Value A」の他に「Value a」や「value a」、「VALUE A」などがあればこれらも取得してしまいます。これは Django が悪いわけではなく、MySQL の挙動が原因です。MySQL で大文字と小文字を区別するためには、VARCHAR や TEXT のカラム定義に BINARY を付ける必要があります。今回、この BINARY を Django で自動的に付けるようにする方法を紹介します。

現象確認

 まず、大文字と小文字を区別できない事を確認してみます。モデルとテストを下記の様に実装します。

models.py
from django.db import models


class Item(models.Model):
    value = models.CharField(max_length=100)
tests.py
from django.test import TestCase


class ItemModelTests(TestCase):

    def test_value_to_distinguish_between_uppercase_and_lowercase(self):
        from .models import Item
        item1 = Item(value='VALUE A')
        item1.save()
        item2 = Item(value='value a')
        item2.save()
        item3 = Item(value='Value A')
        item3.save()

        item = Item.objects.filter(value='Value A')
        self.assertEqual(item.count(), 1)
        self.assertNotEqual(item[0].value, 'VALUE A')
        self.assertNotEqual(item[0].value, 'value a')
        self.assertEqual(item[0].value, 'Value A')

 テストを実行すると、期待通り Fail します。取得した結果が 3 件になっている事から、大文字と小文字を区別できていないと解釈できます。

(venv35)$ python manage.py test

(中略)

F
======================================================================
FAIL: test_value_to_distinguish_between_uppercase_and_lowercase (main.tests.ItemModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/user1/work/django1.10/sample/main/tests.py", line 16, in test_value_to_distinguish_between_uppercase_and_lowercase
    self.assertEqual(item.count(), 1)
AssertionError: 3 != 1

----------------------------------------------------------------------
Ran 1 test in 0.219s

FAILED (failures=1)
Destroying test database for alias 'default'...

カスタムフィールドの定義

 MySQL で実行される、カラム定義に関する SQL は models.Model で定義されたフィールド (CharField や TextField) が元になります。Django がデフォルトで提供している CharField や TextField を拡張して、BINARY キーワードを加えた自前のフィールドクラスを定義します。

models.py
from django.db import models


class CharBinaryField(models.CharField):
    def db_type(self, connection):
        return super(CharBinaryField, self).db_type(connection) + ' binary'


class Item(models.Model):
    value = CharBinaryField(max_length=100)

 テストを実行すると、今度は期待通り Pass します。取得した結果が 1 件だけになっている事、Python からみて大文字と小文字の区別がついている事が確認できました。

(venv35)$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.204s

OK
Destroying test database for alias 'default'...

おわりに

 今回の内容は私が Stack Overflow で質問した内容の解説になります。How to set “BINARY” to VARCHAR column definition for MySQL with django.models?

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください