[PYTHON] Django NullCharField

If you add a unique constraint to a non-essential CharField, a DuplicateError will occur. I was a little addicted to creating a Field that saves Null instead of an empty string when there is no input, so I wrote a memo.

Processing at the time of saving

In django, if CharField or TextField is saved as empty, it will be saved as an empty string instead of Null. If IntegerField etc. is empty, Null is saved properly, so if you check the source of django what you are doing I found the following method in the IntegerField class.

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

This is like a method that converts the value before saving the DB, so if you return None when there is an empty string, Null will be saved.

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

For the time being, this will save Null if there is no input in Form etc.

Processing at the time of acquisition

If you open the admin screen in this state, TypeError will occur. Exception Value: coercing to Unicode: need string or buffer, NoneType found Since it is said that "pass string or buffer", if it is None, it seems that processing to convert it to an empty string is necessary.

I thought I should rewrite the to_string method, but it doesn't work. It seems that it is necessary to pass django.db.models.SubfieldBase to the metaclass after various investigations. The value obtained from DB is entered by setattr (see Model class init method of django.db.models.base). If you pass SubfieldBase to the meta class, the to_python method will be called internally at setattr.

That's why I specified meta and added the processing at the time of acquisition.

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''

check the answer

I thought it might be something, and when I looked it up, it was normal in 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

It was almost there. I checked the null and blank flags with init. Also, it seems that deconstruct overwrite is necessary for migration.

Recommended Posts

Django NullCharField
Django
django update
Django note 4
Django memorandum
django search
Django installation
Django Summary
Django test
Django # 2 (template)
Django Note 5
Django hands-on
Touch django
Django Summary
Django Shoho
Django + Docker
Django Glossary
Django search
Install Django
Django: References
Django Note 1
Django note 3
Django note 2
Django notes
Django environment construction
Django ~ settings.py edition ~
Django Heroku Deploy 1
Django HTML Template # 2
Django Contact Form 2
Django begins part 1
Django model: ManyToManyField
What is Django? .. ..
Models in Django
Django function-based view
Python Django Tutorial (5)
Django Learning Memo
Python Django Tutorial (2)
[Django] as_view () notes
First Django Challenge
django makemigarations createsuperuser
Django related sites
Django version check
Django begins part 4
Django 1.9 for internationalization
CentOS8 --Play --Django
CentOS8 --Install --Django
django tutorial memo
[Django] Redo migrate
django environment construction
Python Django Tutorial (8)
Python Django Tutorial (6)
django default settings
Start Django Tutorial 1
Django HTML template
Django Template Tips
Django girls-3 workflow
Django Project Baseline
Django Heroku Deploy 2
CRUD with Django
Django filter summary
Django + Docker command