カテゴリー
プログラミング

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 を使っています。コメントデータの処理方法の詳細はこちらをご覧ください