2
1

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 NullCharField

Last updated at Posted at 2015-03-05

必須ではない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も必要らしい.

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?