Saya memiliki model Foo yang memiliki kolom bar. Bidang batang harus unik, tetapi memungkinkan null di dalamnya, yang berarti saya ingin mengizinkan lebih dari satu catatan jika bidang batang adalah null
, tetapi jika tidak null
, nilainya harus unik.
Ini model saya:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
Dan berikut adalah SQL yang sesuai untuk tabel:
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)
)
Saat menggunakan antarmuka admin untuk membuat lebih dari 1 objek foo dengan bilah nol, ini memberi saya kesalahan: "Foo dengan bilah ini sudah ada."
Namun ketika saya memasukkan ke dalam database (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
Ini berfungsi, baik-baik saja, ini mengijinkan saya untuk memasukkan lebih dari 1 catatan dengan bar menjadi null, jadi database memperbolehkan saya untuk melakukan apa yang saya inginkan, itu hanya sesuatu yang salah dengan model Django. Ada ide?
EDIT
Portabilitas solusi sejauh DB tidak menjadi masalah, kami senang dengan Postgres. Saya sudah mencoba mengatur unique to a callable, yaitu fungsi saya mengembalikan True / False untuk nilai tertentu dari bar , itu tidak memberikan kesalahan apa pun, namun seamed seperti itu tidak berpengaruh sama sekali.
Sejauh ini, saya telah menghapus specifier unik dari properti bar dan menangani keunikan bar dalam aplikasi, namun tetap mencari solusi yang lebih elegan. Ada rekomendasi?
sumber
def get_db_prep_value(self, value, connection, prepared=False)
sebagai pemanggilan metode. Periksa groups.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ untuk informasi lebih lanjut. Metode berikut juga bekerja untuk saya: def get_prep_value (self, value): if value == "": # jika Django mencoba menyimpan '' string, kirim db None (NULL) return None else: return value #otherwise, just lulus nilaiJawaban:
Django belum menganggap NULL sama dengan NULL untuk tujuan pemeriksaan keunikan sejak tiket # 9039 diperbaiki, lihat:
http://code.djangoproject.com/ticket/9039
Masalahnya di sini adalah bahwa nilai "kosong" yang dinormalisasi untuk formulir CharField adalah string kosong, bukan None. Jadi jika Anda membiarkan field kosong, Anda mendapatkan string kosong, bukan NULL, yang disimpan di DB. String kosong sama dengan string kosong untuk pemeriksaan keunikan, di bawah aturan Django dan database.
Anda dapat memaksa antarmuka admin untuk menyimpan NULL untuk string kosong dengan menyediakan formulir model kustom Anda sendiri untuk Foo dengan metode clean_bar yang mengubah string kosong menjadi None:
class FooForm(forms.ModelForm): class Meta: model = Foo def clean_bar(self): return self.cleaned_data['bar'] or None class FooAdmin(admin.ModelAdmin): form = FooForm
sumber
fields
atauexclude
atribut dalamModelForm
contoh. Anda dapat mengatasi ini dengan menghilangkanMeta
kelas dalam dari ModelForm untuk digunakan di admin. Referensi: docs.djangoproject.com/en/1.10/ref/contrib/admin/…** edit 30/11/2015 : Di python 3,
__metaclass__
variabel module-global tidak lagi didukung . Ditambahkannya, sebagai dariDjango 1.10
yangSubfieldBase
kelas itu ditinggalkan :Oleh karena itu, seperti yang disarankan oleh
from_db_value()
dokumentasi dan contoh ini, solusi ini harus diubah menjadi:class CharNullField(models.CharField): """ Subclass of the CharField that allows empty strings to be stored as NULL. """ description = "CharField that stores NULL but returns ''." def from_db_value(self, value, expression, connection, contex): """ Gets value right out of the db and changes it if its ``None``. """ if value is None: return '' else: return value def to_python(self, value): """ Gets value right out of the db or an instance, and changes it if its ``None``. """ if isinstance(value, models.CharField): # If an instance, just return the instance. return value if value is None: # If db has NULL, convert it to ''. return '' # Otherwise, just return the value. return value def get_prep_value(self, value): """ Catches value right before sending to db. """ if value == '': # If Django tries to save an empty string, send the db None (NULL). return None else: # Otherwise, just pass the value. return value
Saya pikir cara yang lebih baik daripada menimpa clean_data di admin adalah dengan membuat subclass charfield - cara ini tidak peduli bentuk apa pun yang mengakses field, itu akan "berfungsi." Anda dapat menangkap
''
tepat sebelum itu dikirim ke basis data, dan menangkap NULL tepat setelah keluar dari basis data, dan sisa Django tidak akan tahu / peduli. Contoh cepat dan kotor:from django.db import models class CharNullField(models.CharField): # subclass the CharField description = "CharField that stores NULL but returns ''" __metaclass__ = models.SubfieldBase # this ensures to_python will be called def to_python(self, value): # this is the value right out of the db, or an instance # if an instance, just return the instance if isinstance(value, models.CharField): return value if value is None: # if the db has a NULL (None in Python) return '' # convert it into an empty string else: return value # otherwise, just return the value def get_prep_value(self, value): # catches value right before sending to db if value == '': # if Django tries to save an empty string, send the db None (NULL) return None else: # otherwise, just pass the value return value
Untuk proyek saya, saya membuang ini ke dalam
extras.py
file yang berada di root situs saya, lalu saya bisa langsungfrom mysite.extras import CharNullField
kemodels.py
file aplikasi saya . Bidang bertindak seperti CharField - ingatlah untuk menyetelblank=True, null=True
ketika mendeklarasikan bidang, atau jika tidak Django akan melontarkan kesalahan validasi (diperlukan bidang) atau membuat kolom db yang tidak menerima NULL.sumber
CharField
menjadi aCharNullField
, Anda harus melakukannya dalam tiga langkah. Pertama, tambahkannull=True
ke bidang, dan pindahkan itu. Kemudian, lakukan migrasi data untuk memperbarui nilai kosong apa pun sehingga nilainya menjadi null. Terakhir, ubah bidang menjadi CharNullField. Jika Anda mengonversi bidang sebelum Anda melakukan migrasi data, migrasi data Anda tidak akan melakukan apa pun.from_db_value()
tidak boleh memilikicontex
parameter ekstra itu . Seharusnyadef from_db_value(self, value, expression, connection):
Karena saya baru mengenal stackoverflow, saya belum diizinkan untuk membalas jawaban, tetapi saya ingin menunjukkan bahwa dari sudut pandang filosofis, saya tidak dapat setuju dengan jawaban paling populer untuk pertanyaan ini. (oleh Karen Tracey)
OP mengharuskan kolom barnya unik jika memiliki nilai, dan null jika tidak. Maka harus model itu sendiri memastikan ini masalahnya. Ini tidak dapat diserahkan ke kode eksternal untuk memeriksa ini, karena itu berarti dapat dilewati. (Atau Anda bisa lupa untuk memeriksanya jika Anda menulis pandangan baru di masa mendatang)
Oleh karena itu, untuk menjaga kode Anda benar-benar OOP, Anda harus menggunakan metode internal model Foo Anda. Memodifikasi metode save () atau bidang adalah opsi yang bagus, tetapi menggunakan formulir untuk melakukan ini pasti tidak.
Secara pribadi saya lebih suka menggunakan CharNullField yang disarankan, untuk portabilitas ke model yang mungkin saya tentukan di masa depan.
sumber
Perbaikan cepatnya adalah dengan melakukan:
def save(self, *args, **kwargs): if not self.bar: self.bar = None super(Foo, self).save(*args, **kwargs)
sumber
MyModel.objects.bulk_create()
akan melewati metode ini.Solusi lain yang mungkin
class Foo(models.Model): value = models.CharField(max_length=255, unique=True) class Bar(models.Model): foo = models.OneToOneField(Foo, null=True)
sumber
Ini diperbaiki sekarang setelah https://code.djangoproject.com/ticket/4136 diselesaikan. Dalam Django 1.11+ anda dapat menggunakan
models.CharField(unique=True, null=True, blank=True)
tanpa harus mengubah nilai kosong secara manual menjadiNone
.sumber
CharField
tetapi tidak denganTextField
- batasan gagal karena string kosong masih dilewatkan oleh formulir admin.Anda dapat menambahkan
UniqueConstraint
dengan kondisinullable_field=null
dan tidak menyertakan bidang ini dalamfields
daftar. Jika Anda membutuhkan juga kendala dengannullable_field
nilai yang tidaknull
, Anda dapat menambahkan satu tambahan.Catatan: UniqueConstraint ditambahkan sejak django 2.2
class Foo(models.Model): name = models.CharField(max_length=40) bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None) class Meta: constraints = [ # For bar == null only models.UniqueConstraint(fields=['name'], name='unique__name__when__bar__null', condition=Q(bar__isnull=True)), # For bar != null only models.UniqueConstraint(fields=['name', 'bar'], name='unique__name__when__bar__not_null') ]
sumber
Saya baru-baru ini memiliki persyaratan yang sama. Alih-alih subclassing bidang yang berbeda, saya memilih untuk mengganti metode save () pada model saya (bernama 'MyModel' di bawah) sebagai berikut:
def save(self): """overriding save method so that we can save Null to database, instead of empty string (project requirement)""" # get a list of all model fields (i.e. self._meta.fields)... emptystringfields = [ field for field in self._meta.fields \ # ...that are of type CharField or Textfield... if ((type(field) == django.db.models.fields.CharField) or (type(field) == django.db.models.fields.TextField)) \ # ...and that contain the empty string and (getattr(self, field.name) == "") ] # set each of these fields to None (which tells Django to save Null) for field in emptystringfields: setattr(self, field.name, None) # call the super.save() method super(MyModel, self).save()
sumber
Jika Anda memiliki model MyModel dan ingin my_field menjadi Null atau unik, Anda dapat mengganti metode penyimpanan model:
class MyModel(models.Model): my_field = models.TextField(unique=True, default=None, null=True, blank=True) def save(self, **kwargs): self.my_field = self.my_field or None super().save(**kwargs)
Dengan cara ini, bidang tidak boleh kosong hanya akan menjadi tidak kosong atau nol. nulls tidak bertentangan dengan keunikan
sumber
Baik atau buruk, Django menganggap
NULL
setara denganNULL
untuk tujuan pemeriksaan keunikan. Benar-benar tidak ada jalan lain selain menulis implementasi Anda sendiri dari pemeriksaan keunikan yang dianggapNULL
unik tidak peduli berapa kali itu terjadi dalam tabel.(dan perlu diingat bahwa beberapa solusi DB memiliki pandangan yang sama
NULL
, jadi kode yang bergantung pada ide satu DB tentangNULL
mungkin tidak portabel bagi orang lain)sumber