Unique fields that allow nulls in Django

前端 未结 10 1056
闹比i
闹比i 2020-11-28 01:56

I have model Foo which has field bar. The bar field should be unique, but allow nulls in it, meaning I want to allow more than one record if bar field is null,

10条回答
  •  暗喜
    暗喜 (楼主)
    2020-11-28 02:56

    ** edit 11/30/2015: In python 3, the module-global __metaclass__ variable is no longer supported. Additionaly, as of Django 1.10 the SubfieldBase class was deprecated:

    from the docs:

    django.db.models.fields.subclassing.SubfieldBase has been deprecated and will be removed in Django 1.10. Historically, it was used to handle fields where type conversion was needed when loading from the database, but it was not used in .values() calls or in aggregates. It has been replaced with from_db_value(). Note that the new approach does not call the to_python() method on assignment as was the case with SubfieldBase.

    Therefore, as suggested by the from_db_value() documentation and this example, this solution must be changed to:

    class CharNullField(models.CharField):
    
        """
        Subclass of the CharField that allows empty strings to be stored as NULL.
        """
    
        description = "CharField that stores NULL but returns ''."
    
        def from_db_value(self, value, expression, connection, contex):
            """
            Gets value right out of the db and changes it if its ``None``.
            """
            if value is None:
                return ''
            else:
                return value
    
    
        def to_python(self, value):
            """
            Gets value right out of the db or an instance, and changes it if its ``None``.
            """
            if isinstance(value, models.CharField):
                # If an instance, just return the instance.
                return value
            if value is None:
                # If db has NULL, convert it to ''.
                return ''
    
            # Otherwise, just return the value.
            return value
    
        def get_prep_value(self, value):
            """
            Catches value right before sending to db.
            """
            if value == '':
                # If Django tries to save an empty string, send the db None (NULL).
                return None
            else:
                # Otherwise, just pass the value.
                return value
    

    I think a better way than overriding the cleaned_data in the admin would be to subclass the charfield - this way no matter what form accesses the field, it will "just work." You can catch the '' just before it is sent to the database, and catch the NULL just after it comes out of the database, and the rest of Django won't know/care. A quick and dirty example:

    from django.db import models
    
    
    class CharNullField(models.CharField):  # subclass the CharField
        description = "CharField that stores NULL but returns ''"
        __metaclass__ = models.SubfieldBase  # this ensures to_python will be called
    
        def to_python(self, value):
            # this is the value right out of the db, or an instance
            # if an instance, just return the instance
            if isinstance(value, models.CharField):
                return value 
            if value is None:  # if the db has a NULL (None in Python)
                return ''      # convert it into an empty string
            else:
                return value   # otherwise, just return the value
    
        def get_prep_value(self, value):  # catches value right before sending to db
            if value == '':   
                # if Django tries to save an empty string, send the db None (NULL)
                return None
            else:
                # otherwise, just pass the value
                return value  
    

    For my project, I dumped this into an extras.py file that lives in the root of my site, then I can just from mysite.extras import CharNullField in my app's models.py file. The field acts just like a CharField - just remember to set blank=True, null=True when declaring the field, or otherwise Django will throw a validation error (field required) or create a db column that doesn't accept NULL.

提交回复
热议问题