[database] Django의 고유 한 BooleanField 값?

내 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_savedjango.db.models.BooleanFieldTrueFalseTrueFalseTrueTrue

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)의 테이블 ManyToManyFieldA의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)
    

내 경우에 적합하지 않지만 실행 가능한 다른 솔루션 :

@nemocorpclean유효성 검사를 수행하는 메서드를 재정의합니다 . 그러나 어떤 모델이 “하나”인지는보고하지 않으며 사용자 친화적이지 않습니다. 그럼에도 불구하고 특히 누군가 @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")

이렇게하면 기본 관리 양식에서 유효성 검사를 사용할 수 있습니다.