2013-06-23 10 views
5

Dostaję najbardziej dziwny błąd w historii. Mam model osobyDjango unikalny, pusty i pusty CharField podający błąd "już istnieje" na stronie administratora

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) 

Ostatnio dodałem pole twitter_id. Kiedy przejść na stronę admina Django, i starają się zmienić „osoba” w suggested_person, pojawia się następujący błąd:

Person with this Twitter id already exists. 

Uważam, że ten błąd się bardzo dziwne, ponieważ pole Facebook_id przeznaczony jest dokładnie taki sam sposób jako pole Twitter_id.

Jaki może być tego powód?

Odpowiedz

1

Ponieważ masz null=True, blank=True i unique=True, django rozważa None lub puste jako unikalny wpis. Usuń unikalne ograniczenie i obsłuż unikatową część w kodzie.

+1

Usunąłem pole Twitter_id, a następnie ponownie je dodałem używając South.Teraz działa bez żadnych problemów. – noahandthewhale

+2

Django traktuje puste pole jako pusty ciąg '''', a 'unique = True' nie zezwala na wiele wpisów z wartością' ''. Pozwoli to jednak na wielokrotne wpisy z 'twitter_id = None'. – Alasdair

-2

akceptujesz puste wartości i oczekujesz, że będą unikatowe. Oznacza to, że może być tylko jeden wpis z pustym twitter_id

można

  • albo usunąć unikalną contraint
  • wyjąć zaślepkę = True
  • podać wartość domyślną dla pola (ale domyślnie musi być unikalny)
+0

Wartość domyślna nie będzie działać. Znowu ten sam problem. Usunięcie pustego pola spowoduje też więcej problemów. – karthikr

+0

o tak, wyjątkowe ograniczenie. ale domyślna wartość może pochodzić z funkcji lub czegoś. –

16

To jest stary, ale miałem podobny problem właśnie teraz i chociaż będę oferować alternatywne rozwiązanie.

Jestem w sytuacji, w której muszę mieć CharField z null = True, puste = True i unique = True. Jeśli prześlę pusty ciąg w panelu administracyjnym, nie zostanie przesłany, ponieważ pusty ciąg nie jest unikalny.

Aby to naprawić, zastępuję funkcję "clean" w ModelForm, a tam sprawdzam, czy jest to pusty ciąg i zwraca wynik w sposób zręczny.

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

Naprawiono problem dla mnie bez konieczności poświęcania unikalnego atrybutu w polu modelu.

Mam nadzieję, że to pomoże.

EDYTOWANIE: Musisz zmienić miejsce, w którym umieściłem "something_unique_or_null" na nazwie twojego pola. Na przykład "clean_twitter_id".

8

Żadna z odpowiedzi nie opisuje jasno przyczyny problemu.

Zwykle w db można utworzyć pole null=True, unique=True i będzie działać ... ponieważ NULL != NULL. Każda wartość pusta jest nadal uważana za unikatową.

Ale niestety dla CharField s Django zapisze pusty ciąg "" (bo po przesłaniu formularza wszystko wchodzi w Django jako ciągi znaków, a może masz naprawdę chciał zaoszczędzić pusty ciąg "" - Django nie wiem, czy to powinien przekonwertować na None)

Oznacza to, że nie powinieneś używać CharField(unique=True, null=True, blank=True) w Django. Jak zauważyli inni, prawdopodobnie musicie zrezygnować z ograniczenia unikalnego na poziomie db i wykonać własne unikalne kontrole w modelu.

Dla dalszego odniesienia, zobacz tutaj: https://code.djangoproject.com/ticket/4136
(niestety nie jest dobre rozwiązanie zdecydowała w czasie pisania)

+2

W python, 'Brak == Brak'. –

+0

Ups, masz rację, zaktualizuje – Anentropic

+3

Bilet 4136 został już naprawiony. W Django 1.11+ możesz użyć 'CharField (unique = True, null = True, blank = True)' bez ręcznej zamiany pustych wartości na 'None'. – Alasdair

4

Źródłem problemu jest to, że Django utrzymują pustą wartość jako pusty-string, a nie jako wartość null . Aby rozwiązać ten problem, można podklasy Charfield następująco:

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 

Więc get_db_prep_value zrobić to upewnić się, że zerowa zostaje utrwalone

1

ważne jest, aby rozwiązać ten problem na poziomie modelu, a nie na poziomie formularza, ponieważ dane może wchodzić przez API, przez skrypty importowe, z powłoki itp. Minusem ustawienia null=True na CharField jest to, że kolumna może zakończyć się zarówno pustymi ciągami, jak i wartościami NULL, co jest nieco niejednoznaczne, ale generalnie nie stanowi problemu w moim doświadczeniu. Jeśli chcesz żyć z tą niejednoznacznością, oto jak to zrobić w kilku krokach:

1) Ustaw null=True, blank=True na polu i przenieś zmiany.

2) masaż dane tak, że wszystkie istniejące puste struny są zmieniane na wartości null:

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

3) Dodaj niestandardową metodę save() do modelu:

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) Ustaw unique=True na dziedzinie i migrować ją również.

Teraz możesz przechowywać somefield jako pustą lub unikatową wartość, niezależnie od tego, czy używasz administratora, czy jakiejkolwiek innej metody wprowadzania danych.

Jeśli wolisz nie mieć kilka migracje, oto przykład jak to zrobić w jednym migracji:

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), 
     ), 
    ] 
7

w Django 1.11, forma CharFields będzie miał empty_value argument, który pozwala na użycie None, jeśli pole jest puste.

Formularze modeli, w tym administrator Django, will automatically set empty_value=None, jeśli model CharField ma null=True.

W związku z tym można używać razem null=True, blank=True i unique=True bez unikalnego ograniczenia powodującego problemy.