내 models.py가 다음과 같다고 가정합니다.
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField()
내 Character
인스턴스 중 하나만 포함 is_the_chosen_one == True
하고 다른 모든 인스턴스 에는is_the_chosen_one == False
. 이 고유성 제약이 존중되는지 어떻게 가장 잘 확인할 수 있습니까?
데이터베이스, 모델 및 (관리자) 양식 수준에서 제약 조건 준수의 중요성을 고려한 답변에 대한 최고 점수!
답변
이 작업을 수행해야 할 때마다 모델에 대한 저장 방법을 재정의하고 다른 모델에 이미 플래그가 설정되어 있는지 확인하고 해제하도록했습니다.
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField()
def save(self, *args, **kwargs):
if self.is_the_chosen_one:
try:
temp = Character.objects.get(is_the_chosen_one=True)
if self != temp:
temp.is_the_chosen_one = False
temp.save()
except Character.DoesNotExist:
pass
super(Character, self).save(*args, **kwargs)
답변
모델의 저장 방법을 재정의하고 부울을 True로 설정 한 경우 다른 모든 항목이 False로 설정되어 있는지 확인합니다.
from django.db import transaction
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField()
def save(self, *args, **kwargs):
if not self.is_the_chosen_one:
return super(Character, self).save(*args, **kwargs)
with transaction.atomic():
Character.objects.filter(
is_the_chosen_one=True).update(is_the_chosen_one=False)
return super(Character, self).save(*args, **kwargs)
Adam의 유사한 답변을 편집하려고 시도했지만 원래 답변을 너무 많이 변경하여 거부되었습니다. 이 방법은 다른 항목의 검사가 단일 쿼리로 수행되므로보다 간결하고 효율적입니다.
답변
사용자 지정 모델 정리 / 저장을 사용하는 대신 .NET에서 메서드를 재정의 하는 사용자 지정 필드를 만들었습니다 . 대신 다른 필드가 있다면 오류를 제기의 , 나는 다른 모든 필드를 만든 이 있다면 . 또한 필드가 있고 다른 필드가 없으면 오류를 발생시키는 대신 필드를 다음 과 같이 저장했습니다.pre_save
django.db.models.BooleanField
True
False
True
False
True
True
fields.py
from django.db.models import BooleanField
class UniqueBooleanField(BooleanField):
def pre_save(self, model_instance, add):
objects = model_instance.__class__.objects
# If True then set all others as False
if getattr(model_instance, self.attname):
objects.update(**{self.attname: False})
# If no true object exists that isnt saved model, save as True
elif not objects.exclude(id=model_instance.id)\
.filter(**{self.attname: True}):
return True
return getattr(model_instance, self.attname)
# To use with South
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^project\.apps\.fields\.UniqueBooleanField"])
models.py
from django.db import models
from project.apps.fields import UniqueBooleanField
class UniqueBooleanModel(models.Model):
unique_boolean = UniqueBooleanField()
def __unicode__(self):
return str(self.unique_boolean)
답변
다음 솔루션은 약간 추악하지만 작동 할 수 있습니다.
class MyModel(models.Model):
is_the_chosen_one = models.NullBooleanField(default=None, unique=True)
def save(self, *args, **kwargs):
if self.is_the_chosen_one is False:
self.is_the_chosen_one = None
super(MyModel, self).save(*args, **kwargs)
is_the_chosen_one을 False 또는 None으로 설정하면 항상 NULL이됩니다. 원하는만큼 NULL을 가질 수 있지만 True는 하나만 가질 수 있습니다.
답변
여기에있는 답으로 끝을 맺으려고 노력하면서 그중 일부는 동일한 문제를 성공적으로 해결하고 각각 다른 상황에 적합하다는 것을 발견했습니다.
나는 선택할 것이다 :
-
@semente : 가능한 최소한의 Django ORM을 재정의하는 동안 데이터베이스, 모델 및 관리자 양식 수준의 제약 조건을 존중합니다. 또한 그것은 할 수 있습니다
아마돌며 사용되는through
(A)의 테이블ManyToManyField
A의unique_together
상황 .(확인하고보고하겠습니다)class MyModel(models.Model): is_the_chosen_one = models.NullBooleanField(default=None, unique=True) def save(self, *args, **kwargs): if self.is_the_chosen_one is False: self.is_the_chosen_one = None super(MyModel, self).save(*args, **kwargs)
-
@Ellis Percival : 데이터베이스에 한 번만 추가로 히트하고 현재 항목을 선택한 항목으로 받아들입니다. 깨끗하고 우아합니다.
from django.db import transaction class Character(models.Model): name = models.CharField(max_length=255) is_the_chosen_one = models.BooleanField() def save(self, *args, **kwargs): if not self.is_the_chosen_one: # The use of return is explained in the comments return super(Character, self).save(*args, **kwargs) with transaction.atomic(): Character.objects.filter( is_the_chosen_one=True).update(is_the_chosen_one=False) # The use of return is explained in the comments return super(Character, self).save(*args, **kwargs)
내 경우에 적합하지 않지만 실행 가능한 다른 솔루션 :
@nemocorp 는 clean
유효성 검사를 수행하는 메서드를 재정의합니다 . 그러나 어떤 모델이 “하나”인지는보고하지 않으며 사용자 친화적이지 않습니다. 그럼에도 불구하고 특히 누군가 @Flyte만큼 공격적이지 않으려는 경우 매우 좋은 접근 방식입니다.
@ saul.shanabrook 및 @Thierry J. 는 다른 “is_the_one”항목을 변경 False
하거나 ValidationError
. 나는 그것이 절대적으로 필요하지 않는 한 내 Django 설치에 새로운 기능을 적용하는 것을 꺼려합니다.
@daigorocub : Django 신호를 사용합니다. 고유 한 접근 방식이며 Django Signals 사용 방법에 대한 힌트를 제공합니다 . 그러나이 절차를 “분리 된 응용 프로그램”의 일부로 간주 할 수 없기 때문에 이것이 엄격하게 말하면 “적절한”신호 사용인지 여부는 확실하지 않습니다.
답변
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField()
def save(self, *args, **kwargs):
if self.is_the_chosen_one:
qs = Character.objects.filter(is_the_chosen_one=True)
if self.pk:
qs = qs.exclude(pk=self.pk)
if qs.count() != 0:
# choose ONE of the next two lines
self.is_the_chosen_one = False # keep the existing "chosen one"
#qs.update(is_the_chosen_one=False) # make this obj "the chosen one"
super(Character, self).save(*args, **kwargs)
class CharacterForm(forms.ModelForm):
class Meta:
model = Character
# if you want to use the new obj as the chosen one and remove others, then
# be sure to use the second line in the model save() above and DO NOT USE
# the following clean method
def clean_is_the_chosen_one(self):
chosen = self.cleaned_data.get('is_the_chosen_one')
if chosen:
qs = Character.objects.filter(is_the_chosen_one=True)
if self.instance.pk:
qs = qs.exclude(pk=self.instance.pk)
if qs.count() != 0:
raise forms.ValidationError("A Chosen One already exists! You will pay for your insolence!")
return chosen
위의 양식을 관리자에게도 사용할 수 있습니다.
class CharacterAdmin(admin.ModelAdmin):
form = CharacterForm
admin.site.register(Character, CharacterAdmin)
답변
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField()
def clean(self):
from django.core.exceptions import ValidationError
c = Character.objects.filter(is_the_chosen_one__exact=True)
if c and self.is_the_chosen:
raise ValidationError("The chosen one is already here! Too late")
이렇게하면 기본 관리 양식에서 유효성 검사를 사용할 수 있습니다.