簡體   English   中英

Django 唯一,null 和空白 CharField 在管理頁面上給出“已經存在”錯誤

[英]Django unique, null and blank CharField giving 'already exists' error on Admin page

我遇到了有史以來最奇怪的錯誤。 我有一個人 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)

我最近添加了 twitter_id 字段。 當我訪問 Django 管理頁面,並嘗試將“人”更改為建議的人時,出現以下錯誤:

 Person with this Twitter id already exists.

我發現這個錯誤非常奇怪,因為 Facebook_id 字段的設計方式與 Twitter_id 字段完全相同。

這可能是什么原因?

沒有一個答案清楚地描述了問題的根源。

通常在數據庫中,您可以創建一個字段null=True, unique=True並且它會起作用......因為NULL != NULL 所以每個空白值仍然被認為是唯一的。

但不幸的是,對於CharField s Django 將保存一個空字符串"" (因為當您提交表單時,所有內容都以字符串形式進入 Django,您可能真的想保存一個空字符串"" - Django 不知道它是否應該轉換到None

這基本上意味着你不應該在 Django 中使用CharField(unique=True, null=True, blank=True) 正如其他人所指出的,您可能必須放棄數據庫級唯一約束並在模型中進行自己的唯一檢查。

如需進一步參考,請參見此處: https : //code.djangoproject.com/ticket/4136
(不幸的是,在撰寫本文時沒有確定好的解決方案)

這是一個舊的,但我剛才遇到了類似的問題,盡管我會提供替代解決方案。

我處於一種情況,我需要能夠擁有一個帶有 null=True、blank=True 和 unique=True 的 CharField。 如果我在管理面板中提交空字符串,它將不會提交,因為空字符串不是唯一的。

為了解決這個問題,我覆蓋了 ModelForm 中的“clean”函數,在那里我檢查它是否是一個空字符串並相應地返回結果。

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

這為我解決了問題,而不必犧牲模型字段上的唯一屬性。

希望這可以幫助。

編輯:您需要更改我將“something_unique_or_null”放入您的字段名稱的位置。 例如“clean_twitter_id”。

在 Django 1.11 中,表單CharFields將有一個empty_value參數,如果字段為空,它允許您使用None

如果模型的CharField具有null=True ,模型表單,包括 Django 管理員, 將自動設置empty_value=None

因此,您將能夠在模型CharField一起使用null=Trueblank=Trueunique=True ,而不會導致問題的唯一約束。

在模型級別而不是在表單級別解決這個問題很重要,因為數據可以通過 API、導入腳本、shell 等輸入。在 CharField 上設置null=True的缺點是該列可能會結束帶有空字符串和 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),
        ),
    ]

由於您有null=True, blank=Trueunique=True ,django 將None或 blank 作為唯一條目。 去掉唯一性約束,處理代碼中的唯一性部分。

問題的根源在於 Django 將空值持久化為空字符串,而不是 null。 要解決此問題,您可以按如下方式對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將確保 null 被持久化。

您必須在該字段中提供default=None

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

這對我有用。 我在電話號碼中使用了它。 因此,如果輸入並且不是強制性的,它可以是唯一的。

您接受空白值並期望它們是唯一的。 這意味着只能有一個帶有空白 twitter_id 的條目

你可以

  • 要么刪除獨特的約束
  • 刪除空白=真
  • 為字段提供默認值(但默認值必須是唯一的)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM