필드 바가있는 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)
이것은 잘 작동합니다 .bar가 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
관리자에서 cleaned_data를 재정의하는 것보다 더 나은 방법은 charfield를 서브 클래 싱하는 것입니다. 어떤 방식으로 필드에 액세스하든 “그냥 작동합니다.” 당신은 잡을 수 ''
가 데이터베이스로 전송되기 직전에, 그리고 데이터베이스에서 나온 직후 NULL을 잡아, 그리고 장고의 나머지 / 치료를 알 수 없습니다. 빠르고 더러운 예 :
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
내 프로젝트의 경우이 extras.py
파일을 내 사이트의 루트에 있는 파일에 덤프 한 다음 from mysite.extras import CharNullField
내 앱 models.py
파일 에 넣을 수 있습니다 . 필드는 CharField처럼 작동 blank=True, null=True
합니다. 필드를 선언 할 때 설정 해야합니다. 그렇지 않으면 Django가 유효성 검사 오류 (필수 필드)를 발생 시키거나 NULL을 허용하지 않는 DB 열을 만듭니다.
답변
나는 stackoverflow를 처음 사용하기 때문에 아직 답변에 답변을 할 수는 없지만 철학적 관점 에서이 질문에 대한 가장 인기있는 답변에 동의 할 수는 없다는 것을 지적하고 싶습니다. (카렌 트레이시)
OP는 막대 필드에 값이 있으면 고유해야하며, 그렇지 않으면 널입니다. 그런 다음 모델 자체가 이것이 사실인지 확인해야합니다. 이를 확인하기 위해 외부 코드에 맡길 수는 없습니다. 이는 무시할 수 있기 때문입니다. (또는 나중에 새로운 견해를 쓰면 확인하지 않아도됩니다)
따라서 코드를 실제로 OOP로 유지하려면 Foo 모델의 내부 방법을 사용해야합니다. save () 메소드 또는 필드를 수정하는 것이 좋은 옵션이지만 가장 확실한 방법은 양식을 사용하는 것입니다.
개인적으로 저는 차후 정의 할 모델로의 이식성을 위해 CharNullField 제안을 선호합니다.
답변
빠른 수정은 다음과 같습니다.
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
답변
또 다른 가능한 해결책
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
답변
이제 https://code.djangoproject.com/ticket/4136 이 해결되었습니다. Django 1.11 이상에서는 models.CharField(unique=True, null=True, blank=True)
공백 값을로 수동으로 변환하지 않고도 사용할 수 있습니다 None
.
답변
최근에 같은 요구 사항이있었습니다. 다른 필드를 서브 클래 싱하는 대신 다음과 같이 내 모델 (아래 ‘MyModel’)에서 save () 메서드를 재정의하기로했습니다.
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()