2013-07-07 14 views
11

Korzystanie z Django 1.5.1. Python 2.7.3.Niepowtarzalne niepowodzenie wiązania Django razem?

Chciałem zrobić unikalne ograniczenie ze sobą za pomocą pola klucza obcego i pola ślimaka. Więc w moim modelu meta, zrobiłem

foreign_key = models.ForeignKey("self", null=True, default=None) 
slug = models.SlugField(max_length=40, unique=False) 

class Meta: 
    unique_together = ("foreign_key", "slug") 

I nawet sprawdziłem opis tabeli w PostgreSQL (9.1) i ograniczenie oddano do tabeli bazy danych.

-- something like 
"table_name_foreign_key_id_slug_key" UNIQUE CONSTRAINT, btree (foreign_key_id, slug) 

Jednak nadal można zapisać do tabeli bazy danych klucz obcy Brak/zero i duplikaty ciągów.

Na przykład

mogłem wejście i zapisać

# model objects with slug="python" three times; all three foreign_key(s) 
# are None/null because that is their default value 
MO(slug="python").save() 
MO(slug="python").save() 
MO(slug="python").save() 

więc po użyciu unique_together, dlaczego mogę jeszcze trzy wejścia tych samych cenionych wierszy?

Po prostu zgaduję, że może to mieć związek z domyślną wartością None dla pola foreign_key, ponieważ przed unique_together, kiedy miałem unikalny = True na ślimaku, wszystko działało dobrze. Więc jeśli tak jest, jaką wartość domyślną powinienem mieć, która wskazuje wartość zerową, ale także utrzymuje unikalne ograniczenie?

Odpowiedz

16

W Postgresql NULL nie jest równy żadnemu innemu NULL. Dlatego utworzone wiersze nie są takie same (z punktu widzenia PostgreSQL).

Aktualizacja

Masz kilka sposobów radzenia sobie z nim:

  • zabronić wartość Null dla klucza obcego i używać wartości domyślnej
  • zastąpić metodę save swojego modelu sprawdź, czy taki wiersz nie istnieje
  • Zmień standard SQL :)
+1

Jak mogę to poprawić? Czy istnieje wartość, którą mogę przypisać do pola klucza obcego lub coś podobnego do unikatowego, jeśli klucz obcy ma wartość NULL? – Derek

+0

Proszę zobaczyć aktualizację. – Yossi

+0

Czy istnieje wartość domyślna dla klucza obcego w postgresie, że wartość nigdy nie będzie naturalnie używana? – Derek

1

Dodaj do modelu metodę clean, aby móc edytować istniejący wiersz.

def clean(self): 
    queryset = MO.objects.exclude(id=self.id).filter(slug=self.slug) 
    if self.foreign_key is None: 
     if queryset.exists(): 
      raise ValidationError("A row already exists with this slug and no key") 
    else: 
     if queryset.filter(foreign_key=self.foreign_key).exists(): 
      raise ValidationError("This row already exists") 

Uważaj, clean (lub full_clean) nie jest wywoływana przez domyślny save metody.

Uwaga: jeśli umieścisz ten kod w metodzie save, zaktualizowane formularze (takie jak w administratorze) nie będą działać: wystąpi błąd śledzenia związany z wyjątkiem wyjątku ValidationError.

0

Wystarczy ręcznie utworzyć indeks wtórny na slug pola, ale tylko do wartości NULL w foreign_key_id:

CREATE INDEX table_name_unique_null_foreign_key 
    ON table_name (slug) WHERE foreign_key_id is NULL 

Proszę pamiętać, że Django nie obsługuje tej funkcji, więc bez formy niestandardowe walidacji/model dostaniesz czystą IntegrityError/500.

Możliwy duplikat Create unique constraint with null columns

0

As hobbyte wspomniano, „w PostgreSQL NULL nie jest równa każdej innej NULL. Dlatego wiersze tworzone nie są takie same (z perspektywy postgres').”

Innym możliwym sposobem rozwiązania tego problemu jest dodanie niestandardowego sprawdzania poprawności na poziomie widoku w metodzie form_valid.

W views.py:

def form_valid(self, form): 
 

 
    --OTHER VALIDATION AND FIELD VALUE ASSIGNMENT LOGIC-- 
 

 
    if ModelForm.objects.filter(slug=slug,foreign_key=foreign_key: 
 
    form.add_error('field', 
 
     forms.ValidationError(_("Validation error message that shows up in your form. "), 
 
     code='duplicate_row',)) 
 
    return self.form_invalid(form)

Takie podejście jest przydatne, jeśli używasz widoki klasy opartych, zwłaszcza jeśli są automatycznie przypisując wartości do pola, które chcesz ukryć przed użytkownik.

Plusy:

  • Nie trzeba tworzyć fikcyjne wartości domyślne w bazie
  • Nadal można korzystać z formularzy aktualizacji (patrz odpowiedź twył za)

Wady: - Ta nie chroni przed zduplikowanymi wierszami utworzonymi bezpośrednio na poziomie bazy danych. - Jeśli używasz zaplecza administracyjnego Django do tworzenia nowych obiektów MyModel, musisz dodać tę samą logikę walidacji do formularza administratora.

Powiązane problemy