Джанго унікальний, нульовий та порожній CharField, що дає помилку "вже існує" на сторінці адміністратора

Я отримую саму дивну помилку коли-небудь. У мене є модель особистості

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)

Нещодавно я додав поле twitter_id. Коли я отримую доступ до сторінки адміністратора Django та намагаюсь змінити "person" на запропонованого_фактора, я отримую таку помилку:

 Person with this Twitter id already exists.

Я вважаю цю помилку надзвичайно дивною, оскільки поле Facebook_id розроблено так само, як і поле Twitter_id.

Що може бути причиною цього?

5

7 Відповіді

Це старе, але я мав подібний випадок тільки зараз і хотів би запропонувати альтернативне рішення.

Я в ситуації, коли мені потрібно мати CharField з null = True, blank = True and unique = True. Якщо я відправлю порожню рядок на панелі адміністратора, вона не буде подаватися, тому що порожня рядок не є унікальною.

Для того, щоб це виправити, я переопределюю функцію "чиста" в ModelForm, і там я перевіряю, чи це пуста рядок, і повернути результат відповідно.

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

Це вирішило проблему для мене без необхідності жертвувати унікальним атрибутом на полі моделі.

Сподіваюся, це допоможе.

EDIT: Вам потрібно змінити місце, де я поставив "something_unique_or_null" на ім'я вашого поля. Наприклад "clean_twitter_id".

16
додано
Це чудово, але тільки для певної форми. Найкраще рішення наведене нижче від @shacker, оскільки він застосовується безпосередньо до моделі (підвищить виняток для всіх форм, API і т. Д.).
додано Автор François Constant, джерело

Жоден з відповідей чітко не описує корінь проблеми.

Зазвичай в db ви можете створити поле null = True, unique = True , і він буде працювати ... тому що NULL! = NULL . Тому кожне пусте значення все ще вважається унікальним.

Але, на жаль, для CharField s Django збереже порожню рядок "" (оскільки під час відправлення форми все входить в Django як рядки, і ви, можливо, дійсно хотіли зберегти порожня рядок "" - Django не знає, чи слід перетворити його на None )

Це в основному означає, що ви не повинні використовувати CharField (unique = True, null = True, blank = True) в Django. Як інші відзначили, вам, ймовірно, доведеться відмовитися від унікального обмеження на рівні рівня db і зробити свої власні унікальні перевірки в моделі.

Для отримання додаткової інформації дивіться тут: https://code.djangoproject.com/ticket/4136 < br> (на жаль, не було прийнято рішення під час написання)

10
додано
Ой, ви маєте рацію, оновиться
додано Автор Anentropic, джерело
У Python використовується None == None .
додано Автор Vlad V, джерело
Квиток 4136 тепер виправлено. У Django 1.11+ ви можете використовувати CharField (unique = True, null = True, blank = True) без ручного перетворення порожніх значень у None .
додано Автор Alasdair, джерело

У Django 1.11 форма CharFields матиме empty_value , що дозволяє використовувати None , якщо поле порожнє.

Модельні форми, включаючи адміністратора Django,

автоматично встановлюють empty_value = None , якщо у CharField моделі є null = True .

Тому ви зможете використовувати null = True , blank = True і unique = True у вашій моделі CharField без унікального обмеження, що спричиняє проблеми.

7
додано

Важливо вирішити це на рівні моделі, а не на рівні форми, оскільки дані можуть надходити через API, через сценарії імпорту з оболонки тощо. Недоліком налаштування null = True на CharField полягає в тому, що стовпець може закінчитися як порожніми рядками, так і NULL, що трохи неоднозначно, але в цілому не є проблемою в моєму досвіді. Якщо ви хочете жити з такою невизначеністю, ось як це зробити за кілька кроків:

1) Встановіть поле null = True, blank = True і перемістіть його в зміну.

2) Масаж свої дані так, щоб всі існуючі порожні рядки були змінені на NULL:

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

3) Додайте до вашої моделі спеціальний метод save() :

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) Встановіть поле unique = True і перемістіть його в поле.

Тепер ви зможете зберігати somefield як порожню або унікальну цінність, незалежно від того, використовуєте ви адміністратор або будь-який інший спосіб введення даних.

Якщо ви віддаєте перевагу не мати декілька міграцій, наведіть приклад того, як це зробити за одну міграцію:

# -*- 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),
        ),
    ]
4
додано
Я хотів би змінити його на: , якщо self.somefield не є None і self.somefield.strip() == '' , щоб уникнути непотрібного перевизначення, коли нічого не змінилося.
додано Автор lapin, джерело

Коренем проблеми є те, що Джанго зберігає порожнечу як порожню рядок, а не як нуль. Щоб виправити це, ви можете підклас CharField наступним чином:

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

Таким чином, get_db_prep_value зробить це впевненим, що нуль виникає

4
додано

Оскільки у вас є null = True, blank = True і unique = True , django розглядає None або пустий як унікальний запис. Видаліть унікальне обмеження та обробляйте частину унікальності в коді.

1
додано
Я видалив поле Twitter_id, а потім знову додав його, використовуючи Південь. Він прекрасно працює, не даючи мені ніяких проблем
додано Автор noahandthewhale, джерело
Django обробляє порожнє поле як порожню рядок '' , а unique = True не дозволяє декілька записів з значенням '' . Проте, це може дозволити декілька записів за допомогою twitter_id = None .
додано Автор Alasdair, джерело

ви приймаєте порожні значення і очікуєте, що вони будуть унікальними. це означає, що може бути тільки один запис з порожнім twitter_id

ти можеш

  • або вилучіть унікальний протипоказаний
  • видалити blank = True
  • надайте значення за замовчуванням для поля (але за замовчуванням має бути унікальним)
1
додано
Значення за умовчанням не буде працювати. Знову ж питання. Крім того, видалення порожнього буде викликати більше проблем
додано Автор karthikr, джерело
о так, унікальне обмеження. але значення за замовчуванням може належати від функції чи щось.
додано Автор user2404546, джерело
ІТ КПІ - Python
ІТ КПІ - Python
625 учасників

Канал обговорень про всякі штуки зі світу пайтону. Прохання: 0. мати повагу одне до одного; 1. не матюкатися в сторону людей; 2. не захламляти тред повідомленнями по одному слову;