Django NullCharField

必須ではないCharFieldにunique制約をつけるとDuplicateErrorが発生するので，
入力がない場合に空文字ではなくNullを保存するFieldを作ろうとして少しハマったのでメモ書き．

## 保存時の処理

djangoではCharFieldやTextFieldを空で保存されるとNullではなく空文字で保存される．
IntegerFieldなどは空の場合にちゃんとNullが保存されているので何をしているかdjangoのソース確認すると
IntegerFieldクラスに以下のようなメソッドを発見．

```
    def get_prep_value(self, value):
        value = super(IntegerField, self).get_prep_value(value)
        if value is None:
            return None
        return int(value)
```

これがDB保存前に値を変換するメソッドっぽいので，空文字の時にNoneを返してあげればNullを保存してくれるようになる．

```
class NullCharField(models.CharField):
    def get_preg_value(self, value):
        value = Super(NullCharField, self).get_preg_value(value)
        return value if value else None
```

とりあえずこれでFormなどに入力がなかった場合にNullを保存してくれるようになる．

## 取得時の処理

この状態でadmin画面を開くとTypeErrorが発生する．
`Exception Value: coercing to Unicode: need string or buffer, NoneType found`
「stringかbuffer渡せや」って言われているのでNoneだったら空文字に変換する処理が必要っぽい．

to_stringメソッド書き換えればいいって思ってたけど，どうも上手くいかない．
色々調べているとメタクラスにdjango.db.models.SubfieldBaseを渡す必要があるらしい．
DBから取得した値はsetattrで入れてる(django.db.models.baseのModelクラスinitメソッド参照)が，
metaクラスにSubfieldBaseを渡すとsetattr時に内部でto_pythonメソッドが呼ばれるようになる．

というわけでmetaを指定し，取得時の処理を書き加えたのがこれ

```
from django.db import models
from django.utils import six

class NullCharField(six.with_metaclass(models.SubfieldBase, models.CharField)):
    def get_preg_value(self, value):
        value = super(NullCharField, self).get_preg_value(value)
        return value if value else None

    def to_python(self, value):
        value = super(NullCharField, self).to_python(value)
        return value if value is not None else u''
```

***

## 答え合わせ

なんかありそうって思って調べてたらoscarに普通にあった．
https://github.com/django-oscar/django-oscar/blob/master/src/oscar/models/fields/__init__.py

```
class NullCharField(six.with_metaclass(SubfieldBase, CharField)):
    """
    CharField that stores '' as None and returns None as ''
    Useful when using unique=True and forms. Implies null==blank==True.

    When a ModelForm with a CharField with null=True gets saved, the field will
    be set to '': https://code.djangoproject.com/ticket/9590
    This breaks usage with unique=True, as '' is considered equal to another
    field set to ''.
    """
    description = "CharField that stores '' as None and returns None as ''"

    def __init__(self, *args, **kwargs):
        if not kwargs.get('null', True) or not kwargs.get('blank', True):
            raise ImproperlyConfigured(
                "NullCharField implies null==blank==True")
        kwargs['null'] = kwargs['blank'] = True
        super(NullCharField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        val = super(NullCharField, self).to_python(value)
        return val if val is not None else u''

    def get_prep_value(self, value):
        prepped = super(NullCharField, self).get_prep_value(value)
        return prepped if prepped != u"" else None

    def deconstruct(self):
        """
        deconstruct() is needed by Django's migration framework
        """
        name, path, args, kwargs = super(NullCharField, self).deconstruct()
        del kwargs['null']
        del kwargs['blank']
        return name, path, args, kwargs
```

大体あってた．
__init__でnull, blankのフラグチェックをしてた．
あと，migration用にdeconstructのoverwriteも必要らしい．