28

I've been getting the most weird error ever. I have a Person model

class Person(models.Model):
    user = models.OneToOneField(User, primary_key=True)
    facebook_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
    twitter_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
    suggested_person = models.BooleanField(default=False)

I recently added the twitter_id field. When I access the Django admin page, and try to change the 'person' into a suggested_person, I get the following error:

 Person with this Twitter id already exists.

I find this error to be extremely strange because the Facebook_id field is designed the exact same way as the Twitter_id field.

What could be the reason for this?

Alasdair
  • 298,606
  • 55
  • 578
  • 516
deadlock
  • 7,048
  • 14
  • 67
  • 115

8 Answers8

59

None of the answers clearly describe the root of the problem.

Normally in the db you can make a field null=True, unique=True and it will work... because NULL != NULL. So each blank value is still considered unique.

But unfortunately for CharFields Django will save an empty string "" (because when you submit a form everything comes into Django as strings, and you may have really wanted to save an empty string "" - Django doesn't know if it should convert to None)

This basically means you shouldn't use CharField(unique=True, null=True, blank=True) in Django. As others have noted you probably have to give up the db-level unique constraint and do your own unique checks in the model.

For further reference, see here: https://code.djangoproject.com/ticket/4136
(unfortunately no good solution decided at time of writing)

NOTE: As pointed out by @alasdair in a comment, that bug has now been fixed - since Django 1.11 you can use CharField(unique=True, null=True, blank=True) without having to manually convert blank values to None

Anentropic
  • 32,188
  • 12
  • 99
  • 147
  • 41
    Ticket 4136 has now been fixed. In Django 1.11+ you can use `CharField(unique=True, null=True, blank=True)` without having to manually convert blank values to `None`. – Alasdair Sep 17 '16 at 15:07
24

This is an old one but I had a similar issue just now and though I would provide an alternative solution.

I am in a situation where I need to be able to have a CharField with null=True, blank=True and unique=True. If I submit an empty string in the admin panel it will not submit because the blank string is not unique.

To fix this, I override the 'clean' function in the ModelForm, and in there I check if it's a blank string and return the result accordinly.

class MyModelChangeForm(forms.ModelForm):

    class Meta:
        model = models.MyModel
        fields = ['email', 'name', 'something_unique_or_null',]

    def clean_something_unique_or_null(self):
        if self.cleaned_data['something_unique_or_null'] == "":
            return None
        else:
            return self.cleaned_data['something_unique_or_null']

This fixed the problem for me without having to sacrifice the unique attribute on the model field.

Hope this helps.

EDIT: You need to change where I have put "something_unique_or_null" to the name of your field. For example "clean_twitter_id".

LondonAppDev
  • 8,501
  • 8
  • 60
  • 87
  • This is great but only for a particular form. The best solution is below from @shacker as it's applied to the model directly (will raise an exception for all forms, APIs, etc.). – François Constant Aug 08 '18 at 04:52
14

In Django 1.11, form CharFields will have an empty_value argument, which allows you to use None if the field is empty.

Model forms, including the Django admin, will automatically set empty_value=None if the model's CharField has null=True.

Therefore, you will be able to use null=True, blank=True and unique=True together in your model CharField without the unique constraint causing problems.

Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • 5
    In my case (django `2.1.1`) `a = models.CharField(max_length=100, null=True, blank=True, unique=True)` still saves empty strings instead of NULL :( – stelios Dec 11 '18 at 21:58
  • @chefarov if you're stuck please ask a new question. I can't help you in the comments. – Alasdair Dec 11 '18 at 22:16
  • 4
    He is right though. It seems that the second paragraph of your answer is wrong. – David Schumann Jul 24 '19 at 07:47
  • 1
    @DavidSchumann *form CharFields will have an empty_value* - but the second paragraph links to docs that say the same thing. I just tried `name = models.CharField(null=True, blank=True, unique=True, max_length=20)` in Django 3.1, and it saves empty values as `None`, as expected. – Alasdair Aug 07 '20 at 17:08
13

Since you have null=True, blank=True and unique=True, django is considering None or blank as a unique entry. Remove the unique constraint and handle the uniqueness part in the code.

karthikr
  • 97,368
  • 26
  • 197
  • 188
  • 2
    I deleted the Twitter_id field and then re-added it in using South. It's working fine now without giving me any issues – deadlock Jun 23 '13 at 02:27
  • 8
    Django is treating a blank field as the empty string `''`, and the `unique=True` does not allow multiple entries with value `''`. It would however allow multiple entries with `twitter_id=None`. – Alasdair Sep 17 '16 at 14:54
  • See [Anentropic's answer](https://stackoverflow.com/a/29955359/10746224) for a detailed explanation about why this happens, and see [LondonAppDev's answer](https://stackoverflow.com/a/21934494/10746224) for a potential solution. – Lord Elrond Oct 02 '19 at 19:40
12

It's important to solve this at the model level, not at the form level, since data can enter through APIs, through import scripts, from the shell, etc. The downside of setting null=True on a CharField is that the column could end up with both empty strings and NULLs, which is slightly ambiguous but not generally a problem in my experience. If you're willing to live with that ambiguity, here's how to do it in a few steps:

1) Set null=True, blank=True on the field and migrate in the change.

2) Massage your data so that all existing empty strings are changed to NULLs:

items = Foo.objects.all()
for item in items:
  if not item.somefield:
    item.somefield = None
    item.save()

3) Add a custom save() method to your model:

def save(self, *args, **kwargs):
    # Empty strings are not unique, but we can save multiple NULLs
    if not self.somefield:
        self.somefield = None

    super().save(*args, **kwargs)  # Python3-style super()

4) Set unique=True on the field and migrate that in as well.

Now you'll be able to store somefield as empty or as a unique value whether you're using the admin or any other data entry method.

If you prefer not to have several migrations, here's an example of how to do it in a single migration:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models

def set_nulls(apps, schema_editor):
    Event = apps.get_model("events", "Event")
    events = Event.objects.all()
    for e in events:
        if not e.wdid:
            e.wdid = None
            e.save()


class Migration(migrations.Migration):

    dependencies = [
        ('events', '0008_something'),
    ]

    operations = [
        migrations.AlterField(
            model_name='event',
            name='wdid',
            field=models.CharField(blank=True, max_length=32, null=True),
        ),
        migrations.RunPython(set_nulls),
        migrations.AlterField(
            model_name='event',
            name='wdid',
            field=models.CharField(blank=True, max_length=32, null=True, unique=True),
        ),
    ]
Danil
  • 4,781
  • 1
  • 35
  • 50
shacker
  • 14,712
  • 8
  • 89
  • 89
  • I would change it to : `if self.somefield is not None and self.somefield.strip() == ''` to avoid useless override when nothing changed. – lapin May 31 '18 at 11:33
  • I've tried this, but I find that my custom "save" method isn't called when I press save on the Admin page. (And I'm still getting a fail because my empty text box is non-unique.) I wonder if there's some sort of pre-filtering going on at the form level that's preventing the model save? – Apollo Grace Apr 01 '21 at 18:08
7

The root of the problem is that Django persist the empty value as empty-string, and not as null. To fix this, you can subclass CharField as follows:

class CharNullField(models.CharField):
    description = "CharField that stores NULL"
    
    def get_db_prep_value(self, value, connection=None, prepared=False):
        value = super(CharNullField, self).get_db_prep_value(value, connection, prepared)
        if value=="":
            return None
        else:
            return value

So get_db_prep_value will make it sure that null gets persisted.

Paolo
  • 20,112
  • 21
  • 72
  • 113
Vajk Hermecz
  • 5,413
  • 2
  • 34
  • 25
0

You have to provide default=None in the field

facebook_id = models.CharField(max_length=225,unique=True, null=True,blank=True,default=None)

This worked for me . I used it in phone number. So it can be unique if entered and not mandatory.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
NiKHiL
  • 41
  • 3
-1

you are accepting blank values and expect them to be unique. this means there can only be ONE entry with a blank twitter_id

you can

  • either remove the unique contraint
  • remove the blank =True
  • give a default value for the field ( but default needs to be unique)
  • Default value wont work. Again same issue. Also removing blank would cause more issues – karthikr Jun 23 '13 at 02:21
  • oh yes, the unique constraint. but the default value can come from a function or something. –  Jun 23 '13 at 02:23