188👍
Django has not considered NULL
to be equal to NULL
for the purpose of uniqueness checks since ticket #9039 was fixed, see:
http://code.djangoproject.com/ticket/9039
The issue here is that the normalized "blank" value for a form CharField
is an empty string, not None
. So if you leave the field blank, you get an empty string, not NULL
, stored in the DB. Empty strings are equal to empty strings for uniqueness checks, under both Django and database rules.
You can force the admin interface to store NULL
for an empty string by providing your own customized model form for Foo
with a clean_bar
method that turns the empty string into None
:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
def clean_bar(self):
return self.cleaned_data['bar'] or None
class FooAdmin(admin.ModelAdmin):
form = FooForm
66👍
** 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 withfrom_db_value()
.
Note that the new approach does not call theto_python()
method on assignment as was the case withSubfieldBase
.
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.
- [Django]-How to concatenate strings in django templates?
- [Django]-In Django 1.4, do Form.has_changed() and Form.changed_data, which are undocumented, work as expected?
- [Django]-Django Admin – Disable the 'Add' action for a specific model
31👍
You can add UniqueConstraint
with condition of nullable_field=null
and not to include this field in fields
list.
If you need also constraint with nullable_field
wich value is not null
, you can add additional one.
Note: UniqueConstraint was added since django 2.2
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
class Meta:
constraints = [
# For bar == null only
models.UniqueConstraint(fields=['name'], name='unique__name__when__bar__null',
condition=Q(bar__isnull=True)),
# For bar != null only
models.UniqueConstraint(fields=['name', 'bar'], name='unique__name__when__bar__not_null')
]
- [Django]-ForeignKey to abstract class (generic relations)
- [Django]-How to query as GROUP BY in Django?
- [Django]-Django south migration – Adding FULLTEXT indexes
20👍
Because I am new to stackoverflow I am not yet allowed to reply to answers, but I would like to point out that from a philosophical point of view, I can’t agree with the most popular answer tot this question. (by Karen Tracey)
The OP requires his bar field to be unique if it has a value, and null otherwise. Then it must be that the model itself makes sure this is the case. It cannot be left to external code to check this, because that would mean it can be bypassed. (Or you can forget to check it if you write a new view in the future)
Therefore, to keep your code truly OOP, you must use an internal method of your Foo model. Modifying the save() method or the field are good options, but using a form to do this most certainly isn’t.
Personally I prefer using the CharNullField suggested, for portability to models I might define in the future.
- [Django]-Django's Double Underscore
- [Django]-Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger events
- [Django]-How to go from django image field to PIL image and back?
14👍
The quick fix is to do :
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
- [Django]-Django development server reload takes too long
- [Django]-What's the purpose of Django setting ‘SECRET_KEY’?
- [Django]-Django can' t load Module 'debug_toolbar': No module named 'debug_toolbar'
11👍
This is fixed now that https://code.djangoproject.com/ticket/4136 is resolved. In Django 1.11+ you can use models.CharField(unique=True, null=True, blank=True)
without having to manually convert blank values to None
.
- [Django]-Django manage.py runserver invalid syntax
- [Django]-Getting Django admin url for an object
- [Django]-Where is a good place to work on accounts/profile in Django with the Django registration app?
5👍
Another possible solution
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
- [Django]-How do I reuse HTML snippets in a django view
- [Django]-Equivalent of PHP "echo something; exit();" with Python/Django?
- [Django]-Add inline model to django admin site
2👍
If you have a model MyModel and want my_field to be Null or unique, you can override model’s save method:
class MyModel(models.Model):
my_field = models.TextField(unique=True, default=None, null=True, blank=True)
def save(self, **kwargs):
self.my_field = self.my_field or None
super().save(**kwargs)
This way, the field cannot be blank will only be non-blank or null. nulls do not contradict uniqueness
- [Django]-How to get the name of current app within a template?
- [Django]-How to check Django version
- [Django]-How do I raise a Response Forbidden in django
1👍
I recently had the same requirement. Instead of subclassing different fields, I chose to override the save() metod on my model (named ‘MyModel’ below) as follows:
def save(self):
"""overriding save method so that we can save Null to database, instead of empty string (project requirement)"""
# get a list of all model fields (i.e. self._meta.fields)...
emptystringfields = [ field for field in self._meta.fields \
# ...that are of type CharField or Textfield...
if ((type(field) == django.db.models.fields.CharField) or (type(field) == django.db.models.fields.TextField)) \
# ...and that contain the empty string
and (getattr(self, field.name) == "") ]
# set each of these fields to None (which tells Django to save Null)
for field in emptystringfields:
setattr(self, field.name, None)
# call the super.save() method
super(MyModel, self).save()
- [Django]-Django error: got multiple values for keyword argument
- [Django]-How to define two fields "unique" as couple
- [Django]-Images from ImageField in Django don't load in template
0👍
For better or worse, Django considers NULL
to be equivalent to NULL
for purposes of uniqueness checks. There’s really no way around it short of writing your own implementation of the uniqueness check which considers NULL
to be unique no matter how many times it occurs in a table.
(and keep in mind that some DB solutions take the same view of NULL
, so code relying on one DB’s ideas about NULL
may not be portable to others)
- [Django]-Django composite unique on multiple model fields
- [Django]-__init__() got an unexpected keyword argument 'mimetype'
- [Django]-Best practices for getting the most testing coverage with Django/Python?