Django에서 널을 허용하는 고유 필드
필드 바가있는 Foo 모델이 있습니다. 막대 필드는 고유해야하지만 그 안에 널을 허용합니다. 즉, 막대 필드가 null
인 경우 둘 이상의 레코드를 허용하고 싶지만 null
값 이 아닌 경우 값이 고유해야합니다.
내 모델은 다음과 같습니다.
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
다음은 테이블에 해당하는 SQL입니다.
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)
)
관리 인터페이스를 사용하여 bar가 null 인 둘 이상의 foo 객체를 만들 때 "이 막대가있는 푸는 이미 존재합니다."라는 오류가 발생합니다.
그러나 데이터베이스에 삽입 할 때 (PostgreSQL) :
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
이것은 잘 작동합니다. 막대가 null 인 상태에서 둘 이상의 레코드를 삽입 할 수 있으므로 데이터베이스에서 원하는 것을 수행 할 수 있습니다 .Django 모델에는 문제가 있습니다. 어떤 아이디어?
편집하다
DB가 문제가되지 않는 한 솔루션의 이식성은 Postgres에 만족합니다. bar의 특정 값에 대해 True / False를 반환하는 함수 인 callable에 고유 한 설정을 시도했지만 오류가 발생하지 않았지만 전혀 영향을 미치지 않는 것처럼 이음새가 없었습니다.
지금까지 막대 속성 에서 고유 지정자를 제거하고 응용 프로그램에서 막대 고유성을 처리 했지만 여전히 더 우아한 솔루션을 찾고 있습니다. 어떤 추천?
Django는 티켓 # 9039가 수정 된 이후 고유성 검사를 위해 NULL을 NULL과 동일하다고 간주하지 않았습니다.
http://code.djangoproject.com/ticket/9039
여기서 문제는 CharField 양식의 정규화 된 "공백"값이 없음이 아니라 빈 문자열이라는 것입니다. 따라서 필드를 비워두면 NULL이 아닌 빈 문자열이 DB에 저장됩니다. 빈 문자열은 Django 및 데이터베이스 규칙 모두에서 고유성 검사를 위해 빈 문자열과 같습니다.
빈 문자열을 없음으로 바꾸는 clean_bar 메소드를 사용하여 Foo에 대한 사용자 정의 된 모델 양식을 제공하여 관리자 인터페이스가 빈 문자열에 대해 NULL을 저장하도록 할 수 있습니다.
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
** 2015 년 11 월 30 일 편집 : Python 3에서는 모듈 전역 __metaclass__
변수가 더 이상 지원되지 않습니다 . Additionaly은, 현재의 클래스했다 되지 않습니다 :Django 1.10
SubfieldBase
로부터 문서 :
django.db.models.fields.subclassing.SubfieldBase
더 이상 사용되지 않으며 Django 1.10에서 제거됩니다. 역사적으로 데이터베이스에서로드 할 때 유형 변환이 필요한 필드를 처리하는 데 사용되었지만.values()
호출 또는 집계 에는 사용되지 않았습니다 . 로 교체되었습니다from_db_value()
. 새로운 접근 방식to_python()
은의 경우와 같이 할당시 메소드 를 호출하지 않습니다SubfieldBase
.
따라서 from_db_value()
설명서 및이 예제 에서 제안한대로이 솔루션은 다음과 같이 변경해야합니다.
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
I think a better way than overriding the cleaned_data in the admin would be to subclass the charfield - this way no matter what form accesses the field, it will "just work." You can catch the ''
just before it is sent to the database, and catch the NULL just after it comes out of the database, and the rest of Django won't know/care. A quick and dirty example:
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
For my project, I dumped this into an extras.py
file that lives in the root of my site, then I can just from mysite.extras import CharNullField
in my app's models.py
file. The field acts just like a CharField - just remember to set blank=True, null=True
when declaring the field, or otherwise Django will throw a validation error (field required) or create a db column that doesn't accept NULL.
Because I am new to stackoverflow I am not yet allowed to reply to answers, but I would like to point out that from a philosophical point of view, I can't agree with the most popular answer tot this question. (by Karen Tracey)
The OP requires his bar field to be unique if it has a value, and null otherwise. Then it must be that the model itself makes sure this is the case. It cannot be left to external code to check this, because that would mean it can be bypassed. (Or you can forget to check it if you write a new view in the future)
Therefore, to keep your code truly OOP, you must use an internal method of your Foo model. Modifying the save() method or the field are good options, but using a form to do this most certainly isn't.
Personally I prefer using the CharNullField suggested, for portability to models I might define in the future.
The quick fix is to do :
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
Another possible solution
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
For better or worse, Django considers NULL
to be equivalent to NULL
for purposes of uniqueness checks. There's really no way around it short of writing your own implementation of the uniqueness check which considers NULL
to be unique no matter how many times it occurs in a table.
(and keep in mind that some DB solutions take the same view of NULL
, so code relying on one DB's ideas about NULL
may not be portable to others)
I recently had the same requirement. Instead of subclassing different fields, I chose to override the save() metod on my model (named 'MyModel' below) as follows:
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()
If you have a model MyModel and want my_field to be Null or unique, you can override model's save method:
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)
This way, the field cannot be blank will only be non-blank or null. nulls do not contradict uniqueness
참고URL : https://stackoverflow.com/questions/454436/unique-fields-that-allow-nulls-in-django
'Programming' 카테고리의 다른 글
npm : 패키지 설치 후 스크립트 비활성화 (0) | 2020.07.15 |
---|---|
JavaScript를 사용하여 브라우저에서 Word 문서 (.doc, .docx)를 어떻게 렌더링합니까? (0) | 2020.07.15 |
django의 urls.py에서 직접 명명 된 URL 패턴으로 리디렉션 하시겠습니까? (0) | 2020.07.15 |
bash에서 특수 변수를 수동으로 확장하는 방법 (예 : ~ 물결표) (0) | 2020.07.15 |
Linux 쉘에서 변수로 나누는 방법은 무엇입니까? (0) | 2020.07.15 |