Mam model Foo, który ma pasek pola. Pole słupka powinno być unikalne, ale dopuszczać w nim wartości null, co oznacza, że chcę zezwolić na więcej niż jeden rekord, jeśli pole słupka jest null
, ale jeśli tak nie jest, null
wartości muszą być unikalne.
Oto mój model:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
A oto odpowiedni kod SQL dla tabeli:
CREATE TABLE appl_foo
(
id serial NOT NULL,
"name" character varying(40) NOT NULL,
bar character varying(40),
CONSTRAINT appl_foo_pkey PRIMARY KEY (id),
CONSTRAINT appl_foo_bar_key UNIQUE (bar)
)
Kiedy używam interfejsu administratora do tworzenia więcej niż 1 obiektów foo, gdzie bar jest pusty, pojawia się błąd: „Foo z tym paskiem już istnieje”.
Jednak kiedy wstawiam do bazy danych (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
To działa, po prostu dobrze, pozwala mi wstawić więcej niż 1 rekord z pustym słupkiem, więc baza danych pozwala mi robić co chcę, po prostu coś jest nie tak z modelem Django. Jakieś pomysły?
EDYTOWAĆ
Przenośność rozwiązania o ile DB nie jest problemem, jesteśmy zadowoleni z Postgres. Próbowałem ustawić unikalny dla wywoływanego, co było moją funkcją zwracającą True / False dla określonych wartości słupka , nie dawało to żadnych błędów, jednak zszywane tak, jakby nie miało żadnego efektu.
Do tej pory usunąłem unikalny specyfikator z właściwości paska i obsługuję unikalność paska w aplikacji, jednak nadal szukam bardziej eleganckiego rozwiązania. Jakieś zalecenia?
źródło
def get_db_prep_value(self, value, connection, prepared=False)
jako wywołania metody. Sprawdzić groups.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ więcej informacji. U mnie też działa następująca metoda: def get_prep_value (self, value): if value == "": #if Django spróbuje zapisać ciąg „”, wyślij db None (NULL) return None else: return value #otherwise, just przekazać wartośćOdpowiedzi:
Django nie uważa NULL za równe NULL do celów kontroli unikalności, ponieważ bilet nr 9039 został naprawiony, zobacz:
http://code.djangoproject.com/ticket/9039
Problem polega na tym, że znormalizowana wartość „pusta” dla formularza CharField jest pustym ciągiem, a nie None. Więc jeśli zostawisz to pole puste, otrzymasz pusty ciąg, a nie NULL, przechowywany w DB. Puste ciągi są równe pustym ciągom do sprawdzania unikalności, zarówno w ramach Django, jak i reguł bazy danych.
Możesz zmusić interfejs administratora do przechowywania NULL dla pustego ciągu, dostarczając własny, dostosowany formularz modelu dla Foo z metodą clean_bar, która zamienia pusty ciąg na None:
źródło
fields
orexclude
wModelForm
instancjach. Możesz obejść ten problem, pomijającMeta
klasę wewnętrzną z ModelForm do użytku w admin. Źródła** edycja 30.11.2015 : W Pythonie 3
__metaclass__
zmienna globalna modułu nie jest już obsługiwana . Dodatkowo, jak zDjango 1.10
tejSubfieldBase
klasy została zaniechana :Dlatego jak sugeruje
from_db_value()
dokumentacja i ten przykład , to rozwiązanie należy zmienić na:Myślę, że lepszym sposobem niż nadpisanie clean_data w adminie byłoby podklasa charfielda - w ten sposób bez względu na to, jaki formularz uzyskuje dostęp do pola, będzie "po prostu działać". Możesz złapać to
''
tuż przed wysłaniem do bazy danych i złapać NULL zaraz po tym, jak wyjdzie z bazy danych, a reszta Django nie będzie wiedzieć / dbać. Szybki i brudny przykład:W przypadku mojego projektu wrzuciłem to do
extras.py
pliku, który znajduje się w katalogu głównym mojej witryny, a następnie mogę po prostufrom mysite.extras import CharNullField
wmodels.py
pliku mojej aplikacji . Pole zachowuje się jak CharField - pamiętaj tylko, aby ustawić jeblank=True, null=True
podczas deklarowania pola, w przeciwnym razie Django zgłosi błąd walidacji (pole wymagane) lub utworzy kolumnę db, która nie akceptuje NULL.źródło
CharField
na aCharNullField
, musisz to zrobić w trzech krokach. Najpierw dodaj danenull=True
do pola i przeprowadź migrację. Następnie przeprowadź migrację danych, aby zaktualizować wszystkie puste wartości, tak aby były puste. Na koniec przekonwertuj pole na CharNullField. Jeśli przekonwertujesz pole przed migracją danych, migracja danych nic nie da.from_db_value()
nie powinno mieć tego dodatkowegocontex
parametru. Powinno byćdef from_db_value(self, value, expression, connection):
Ponieważ jestem nowy w stackoverflow, nie mogę jeszcze odpowiadać na odpowiedzi, ale chciałbym zwrócić uwagę, że z filozoficznego punktu widzenia nie mogę zgodzić się z najpopularniejszą odpowiedzią na to pytanie. (przez Karen Tracey)
OP wymaga, aby jego pole słupka było unikalne, jeśli ma wartość, lub zerowe w przeciwnym razie. W takim razie musi być tak, że sam model zapewnia, że tak jest. Nie można pozostawić tego kodu zewnętrznemu, aby to sprawdzić, ponieważ oznaczałoby to, że można go ominąć. (Lub możesz zapomnieć o sprawdzeniu tego, jeśli napiszesz nowy widok w przyszłości)
Dlatego, aby Twój kod był prawdziwie OOP, musisz użyć wewnętrznej metody swojego modelu Foo. Modyfikacja metody save () lub pola to dobre opcje, ale użycie formularza do tego z pewnością nie.
Osobiście wolę używać sugerowanego CharNullField, aby móc przenosić się do modeli, które mogę zdefiniować w przyszłości.
źródło
Szybka naprawa to:
źródło
MyModel.objects.bulk_create()
spowoduje obejście tej metody.Inne możliwe rozwiązanie
źródło
Zostało to naprawione teraz, gdy https://code.djangoproject.com/ticket/4136 jest rozwiązane. W Django 1.11+ możesz używać
models.CharField(unique=True, null=True, blank=True)
bez konieczności ręcznego konwertowania pustych wartości naNone
.źródło
Niedawno miałem ten sam wymóg. Zamiast tworzyć podklasy różnych pól, zdecydowałem się zastąpić metodę save () w moim modelu (o nazwie „MójModel” poniżej) w następujący sposób:
źródło
Jeśli masz model MyModel i chcesz, aby my_field miało wartość Null lub unikalną, możesz nadpisać metodę zapisu modelu:
W ten sposób pole nie może być puste, będzie tylko niepuste lub puste. wartości zerowe nie zaprzeczają wyjątkowości
źródło
Lepiej lub gorzej, Django uważa
NULL
za równoważne doNULL
celów kontroli unikalności. Naprawdę nieNULL
da się tego obejść, jak tylko napisać własną implementację kontroli niepowtarzalności, która uważa się za unikalną, bez względu na to, ile razy występuje w tabeli.(i pamiętaj, że niektóre rozwiązania DB mają ten sam pogląd
NULL
, więc kod oparty na pomysłach jednej bazy danychNULL
może nie być przenośny dla innych)źródło
Możesz dodać
UniqueConstraint
waruneknullable_field=null
i nie uwzględniać tego pola nafields
liście. Jeśli potrzebujesz również ograniczenia, któregonullable_field
wartość nie jestnull
, możesz dodać dodatkowe.Uwaga: UniqueConstraint została dodana od wersji django 2.2
źródło