Django में अद्वितीय बूलियनफिल्ड मूल्य?


87

मान लीजिए कि मेरा मॉडल थिंकपैड ऐसा है:

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। मैं यह कैसे सुनिश्चित कर सकता हूं कि इस विशिष्ट बाधा का सम्मान किया जाए?

डेटाबेस, मॉडल और (व्यवस्थापक) प्रपत्र स्तरों पर बाधा का सम्मान करने के महत्व को ध्यान में रखने वाले उत्तरों के शीर्ष अंक!


4
अच्छा प्रश्न। मैं भी उत्सुक हूँ अगर इसके लिए इस तरह की बाधा स्थापित करना संभव है। मुझे पता है कि अगर आप इसे केवल एक अद्वितीय बाधा बनाते हैं, तो आप अपने डेटाबेस में केवल दो संभावित पंक्तियों के साथ समाप्त हो जाएंगे ;-)
आंद्रे मिलर

जरूरी नहीं: यदि आप एक NullBooleanField का उपयोग करते हैं, तो आपके पास होना चाहिए: (एक सत्य, एक गलत, कितने भी NULLs)।
मैथ्यू Schinckel

मेरे शोध के अनुसार , @semente उत्तर, डेटाबेस, मॉडल और (व्यवस्थापक) प्रपत्र स्तरों में बाधा का सम्मान करने के महत्व को ध्यान में रखता है, जबकि यह एक throughतालिका के लिए भी एक महान समाधान प्रदान करता है जो ManyToManyFieldएक unique_togetherबाधा की जरूरत है ।
रतिरु

जवाबों:


66

जब भी मुझे इस कार्य को पूरा करने की आवश्यकता होती है, तो मैंने जो किया है वह मॉडल के लिए सहेजें पद्धति को ओवरराइड करता है और इसकी जांच करता है कि क्या किसी अन्य मॉडल में ध्वज पहले से सेट है (और इसे बंद करें)।

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)

3
मैं सिर्फ 'डिफ सेव (सेल्फ):' टू: 'डिफ सेव (सेल्फ, * आर्ग्स, ** क्वार्ग्स) को बदलूंगा:'
मेराक

8
मैंने इसे बदलने के save(self)लिए इसे संपादित करने की कोशिश की, save(self, *args, **kwargs)लेकिन संपादन अस्वीकार कर दिया गया था। किसी भी समीक्षक को यह समझाने में समय लग सकता है कि - चूंकि यह Django के सर्वोत्तम अभ्यास के अनुरूप होगा।
scytale

14
मैंने कोशिश को छोड़कर संपादन को छोड़कर / प्रक्रिया को और अधिक कुशल बनाने के लिए संपादन की कोशिश की, लेकिन इसे अस्वीकार कर दिया गया था .. get()अक्षर वस्तु के बजाय और फिर save()इसे फिर से करने के लिए, आपको बस फ़िल्टर करने और अपडेट करने की आवश्यकता है, जो सिर्फ एक SQL क्वेरी का उत्पादन करता है और DB को बनाए रखने में मदद करता है: if self.is_the_chosen_one:<newline> Character.objects.filter(is_the_chosen_one=True).update(is_the_chosen_one=False)<newline>super(Character, self).save(*args, **kwargs)
Ellis Percival

2
मैं उस कार्य को पूरा करने के लिए किसी भी बेहतर तरीके का सुझाव नहीं दे सकता, लेकिन मैं यह कहना चाहता हूं कि, यदि आप वेब एप्लिकेशन चला रहे हैं, तो कभी भी सेव या क्लीन तरीकों पर भरोसा न करें, जिसे आप एक ही समय में एक समापन बिंदु के लिए कुछ अनुरोध ले सकते हैं। आपको अभी भी डेटाबेस स्तर पर सुरक्षित तरीके से लागू करना होगा।
u.unver34 15

1
नीचे एक बेहतर जवाब है। एलिस पेर्सिवल का उत्तर उपयोग करता है transaction.atomicजो यहां महत्वपूर्ण है। यह एकल क्वेरी का उपयोग करके भी अधिक कुशल है।
अलेक्सांद्रि

33

मैंने मॉडल के सहेजने की विधि को ओवरराइड कर लिया है और यदि आपने बूलियन को True पर सेट किया है, तो सुनिश्चित करें कि अन्य सभी गलत पर सेट हैं।

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)

मैंने एडम द्वारा इसी तरह के उत्तर को संपादित करने की कोशिश की, लेकिन मूल उत्तर के बहुत अधिक बदलने के लिए इसे अस्वीकार कर दिया गया। यह तरीका अधिक सफल और कुशल है क्योंकि अन्य प्रविष्टियों की जाँच एक ही प्रश्न में की जाती है।


7
मुझे लगता है कि यह सबसे अच्छा जवाब है, लेकिन मैं saveएक @transaction.atomicलेनदेन में लपेटने का सुझाव दूंगा । क्योंकि ऐसा हो सकता है कि आप सभी झंडे हटा दें, लेकिन तब बचत विफल हो जाती है और आप सभी पात्रों को चुन नहीं पाते हैं।
मैटर

ऐसा कहने के लिए धन्यवाद। आप बिल्कुल सही हैं और मैं उत्तर को अपडेट करूंगा।
एलिस पेर्सिवल

@ मैटर @transaction.atomicजाति की स्थिति से भी बचाता है।
पावेल फुरमानीक

1
सभी के बीच सबसे अच्छा समाधान!
आर्टुरो

1
लेन-देन के बारे में। परमाणु ने एक सजावटकर्ता के बजाय संदर्भ प्रबंधक का उपयोग किया। मुझे हर मॉडल को बचाने के लिए परमाणु लेनदेन का उपयोग करने का कोई कारण नहीं दिखता है क्योंकि यह केवल मायने रखता है अगर बूलियन क्षेत्र सही है। मेरा सुझाव with transaction.atomic:है कि अगर इफ के अंदर बचत के साथ-साथ इफ स्टेटमेंट का उपयोग किया जाए। फिर एक और ब्लॉक जोड़ रहा है और दूसरे ब्लॉक में भी बचत कर रहा है।
एलेक्सभंडारी

29

कस्टम मॉडल सफाई / बचत का उपयोग करने के बजाय, मैंने एक कस्टम फ़ील्ड बनाया जिस 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)

2
यह अन्य तरीकों की तुलना में कहीं अधिक साफ दिखता है
पिस्ताचे

2
मैं इस समाधान को भी पसंद करता हूं, हालांकि यह ऑब्जेक्ट्स के लिए संभावित रूप से खतरनाक लगता है। ऐसे अन्य सभी ऑब्जेक्ट्स को उस स्थिति में गलत पर सेट करें जहां मॉडल UniqueBoolean True है। यूनिकबुलियनफिल्ड ने यह बताने के लिए कि क्या अन्य वस्तुओं को गलत पर सेट किया जाना चाहिए या यदि कोई त्रुटि उठाई जानी चाहिए (अन्य समझदार विकल्प), तो यह बताने के लिए एक वैकल्पिक तर्क लिया कि और भी बेहतर होगा। इसके अलावा, elif है, जहां आप सही करने के लिए विशेषता सेट करना चाहते हैं में अपनी टिप्पणी को देखते हुए, मुझे लगता है कि आप बदलना चाहिए Return Trueकरने के लिएsetattr(model_instance, self.attname, True)
एंड्रयू चेस

2
UniqueBooleanField वास्तव में अद्वितीय नहीं है क्योंकि आपके पास जितने चाहें उतने गलत मूल्य हो सकते हैं। यकीन नहीं होता कि बेहतर नाम क्या होगा ... OneTrueBooleanField? जो मैं वास्तव में चाहता हूं, वह एक विदेशी कुंजी के साथ संयोजन में इसे सक्षम करने में सक्षम होना चाहिए ताकि मेरे पास एक बूलियनफिल्ड हो सकता है जिसे केवल प्रति बार एक बार सच होने की अनुमति दी गई थी (उदाहरण के लिए एक क्रेडिटकार्ड में "प्राथमिक" फ़ील्ड और उपयोगकर्ता के लिए एक FK है) उपयोगकर्ता / प्राथमिक संयोजन उपयोग के अनुसार एक बार सत्य है)। उस स्थिति के लिए मुझे लगता है कि आदम का जवाब अधिक बचत मेरे लिए अधिक सीधा होगा।
एंड्रयू चेस

1
यह ध्यान दिया जाना चाहिए कि यह विधि आपको trueकेवल एक trueपंक्ति के रूप में सेट की गई पंक्तियों के साथ किसी स्थिति में समाप्त करने की अनुमति देती है ।
rblk

11

निम्नलिखित समाधान थोड़ा बदसूरत है, लेकिन काम कर सकता है:

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 को गलत पर सेट करते हैं या कोई नहीं तो यह हमेशा NULL होगा। आप जितना चाहें उतना NULL हो सकते हैं, लेकिन आपके पास केवल एक True हो सकता है।


1
पहला उपाय मैंने भी सोचा। NULL हमेशा अद्वितीय होता है इसलिए आप हमेशा एक से अधिक NULL वाला कॉलम रख सकते हैं।
केलीसिन

10

यहां जवाबों के साथ समाप्त करने की कोशिश कर रहा हूं, मुझे लगता है कि उनमें से कुछ एक ही मुद्दे को सफलतापूर्वक संबोधित करते हैं और हर एक अलग परिस्थितियों में उपयुक्त है:

मुझे चुनना होगा:

  • @semente : डेटाबेस, मॉडल और व्यवस्थापन स्तरों में बाधा का सम्मान करता है जबकि यह Django ORM को कम से कम संभव बनाता है। इसके अलावा यह कर सकते हैंशायदthroughएक ManyToManyFieldमें एक तालिका के अंदर इस्तेमाल किया जा 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)
    
  • @ एलीस पर्सीवल : डेटाबेस को केवल एक अतिरिक्त समय देता है और चुने हुए के रूप में वर्तमान प्रविष्टि को स्वीकार करता है। साफ और सुरुचिपूर्ण।

    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 सिग्नल का उपयोग करने का एक संकेत देता है । हालाँकि, मुझे यकीन नहीं है कि यह एक आम तौर पर बोल रहा है- "संकेतों का उचित" उपयोग क्योंकि मैं इस प्रक्रिया को "डिकोड किए गए एप्लिकेशन" के हिस्से के रूप में नहीं मान सकता।


समीक्षा के लिए धन्यवाद! यदि आपने अपने कोड को यहां भी अपडेट करना चाहते हैं, तो मैंने अपने जवाब में से एक पर आधारित कुछ टिप्पणियों को अपडेट किया है।
एलिस पेरिवल

@EllisPercival आपको संकेत के लिए धन्यवाद! मैंने तदनुसार कोड अपडेट किया। हालांकि यह ध्यान रखें कि मॉडल। मोडेल.सेव () कुछ वापस नहीं करता है।
रतीरु

कोई बात नहीं। यह ज्यादातर अपनी लाइन पर पहला रिटर्न होने से बचाने के लिए है। आपका संस्करण वास्तव में गलत है, क्योंकि इसमें परमाणु लेनदेन में .save () शामिल नहीं है। इसके अलावा, यह 'लेनदेन के साथ होना चाहिए। परमाणु ():' के बजाय।
एलिस पर्किवल

1
@EllisPercival ओके, धन्यवाद! वास्तव में, हमें सब कुछ वापस रोल करने की ज़रूरत है, save()ऑपरेशन विफल होना चाहिए !
रतिरु

6
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)

4
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")

ऐसा करने से सत्यापन को मूल व्यवस्थापक प्रपत्र में उपलब्ध हो गया


4

Django संस्करण 2.2 के बाद अपने मॉडल में इस तरह की बाधा को जोड़ना सरल है। आप सीधे उपयोग कर सकते हैं UniqueConstraint.conditionDjango डॉक्स

बस अपने मॉडलों class Metaको इस तरह से ओवरराइड करें :

class Meta:
    constraints = [
        UniqueConstraint(fields=['is_the_chosen_one'], condition=Q(is_the_chosen_one=True), name='unique_is_the_chosen_one')
    ]

2

और बस यही।

def save(self, *args, **kwargs):
    if self.default_dp:
        DownloadPageOrder.objects.all().update(**{'default_dp': False})
    super(DownloadPageOrder, self).save(*args, **kwargs)

2

शाऊल के समान दृष्टिकोण का उपयोग करना, लेकिन थोड़ा अलग उद्देश्य:

class TrueUniqueBooleanField(BooleanField):

    def __init__(self, unique_for=None, *args, **kwargs):
        self.unique_for = unique_for
        super(BooleanField, self).__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        value = super(TrueUniqueBooleanField, self).pre_save(model_instance, add)

        objects = model_instance.__class__.objects

        if self.unique_for:
            objects = objects.filter(**{self.unique_for: getattr(model_instance, self.unique_for)})

        if value and objects.exclude(id=model_instance.id).filter(**{self.attname: True}):
            msg = 'Only one instance of {} can have its field {} set to True'.format(model_instance.__class__, self.attname)
            if self.unique_for:
                msg += ' for each different {}'.format(self.unique_for)
            raise ValidationError(msg)

        return value

यह कार्यान्वयन ValidationErrorट्रू के मान के साथ एक और रिकॉर्ड को बचाने का प्रयास करते समय उठाएगा ।

इसके अलावा, मैंने उस unique_forतर्क को जोड़ा है जो मॉडल में किसी भी अन्य क्षेत्र में सेट किया जा सकता है, केवल एक ही मान के साथ रिकॉर्ड के लिए वास्तविक-अद्वितीयता की जांच करने के लिए, जैसे:

class Phone(models.Model):
    user = models.ForeignKey(User)
    main = TrueUniqueBooleanField(unique_for='user', default=False)

1

क्या मुझे अपने प्रश्न का उत्तर देने के लिए अंक मिलते हैं?

समस्या यह थी कि वह खुद को पाश में पा रहा था, जिसके द्वारा तय किया गया था:

    # is this the testimonial image, if so, unselect other images
    if self.testimonial_image is True:
        others = Photograph.objects.filter(project=self.project).filter(testimonial_image=True)
        pdb.set_trace()
        for o in others:
            if o != self: ### important line
                o.testimonial_image = False
                o.save()

नहीं, आपके अपने प्रश्न का उत्तर देने और उस उत्तर को स्वीकार करने के लिए कोई अंक नहीं है। हालाँकि, ऐसे बिंदु हैं जो किसी ने आपके उत्तर को बढ़ा दिया है। :)
dandan78

क्या आप वाकई अपने प्रश्न का उत्तर यहां देने के लिए नहीं हैं ? मूल रूप से आप और @sampablokuper का एक ही सवाल था
j_syk

1

मैंने इनमें से कुछ समाधानों की कोशिश की, और एक और एक के साथ समाप्त हो गया, बस कोड की कमी के लिए (रूपों को ओवरराइड करने या विधि को बचाने के लिए नहीं है)। इस कार्य के लिए, यह क्षेत्र विशिष्ट नहीं हो सकता है, लेकिन संकेत यह सुनिश्चित करता है कि ऐसा होता है।

# making default_number True unique
@receiver(post_save, sender=Character)
def unique_is_the_chosen_one(sender, instance, **kwargs):
    if instance.is_the_chosen_one:
        Character.objects.all().exclude(pk=instance.pk).update(is_the_chosen_one=False)

0

शुरुआती लोगों के लिए चीजों को कम जटिल बनाने के लिए 2020 अपडेट:

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField(blank=False, null=False, default=False)

    def save(self):
         if self.is_the_chosen_one == True:
              items = Character.objects.filter(is_the_chosen_one = True)
              for x in items:
                   x.is_the_chosen_one = False
                   x.save()
         super().save()

बेशक, यदि आप चाहते हैं कि अद्वितीय बूलियन गलत हो, तो आप बस फाल्स के साथ ट्रू के हर उदाहरण को स्वैप करेंगे और इसके विपरीत।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.