अनोखे क्षेत्र जो Django में नल की अनुमति देते हैं


135

मेरे पास मॉडल फू है जिसमें फील्ड बार है। बार क्षेत्र अद्वितीय होना चाहिए, लेकिन इसमें नल की अनुमति दें, जिसका अर्थ है कि मैं एक से अधिक रिकॉर्ड की अनुमति देना चाहता हूं यदि बार क्षेत्र है 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)

और यहाँ तालिका के लिए इसी एसक्यूएल है:

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

व्यवस्थापक इंटरफ़ेस का उपयोग करते समय 1 से अधिक फू ऑब्जेक्ट्स बनाने के लिए जहां बार नल है, यह मुझे एक त्रुटि देता है: "इस बार के साथ फू मौजूद है।"

हालाँकि जब मैं डेटाबेस (PostgreSQL) में सम्मिलित करता हूँ:

insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)

यह काम करता है, बस ठीक है, यह मुझे 1 से अधिक रिकॉर्ड डालने की अनुमति देता है बार के साथ शून्य, इसलिए डेटाबेस मुझे वह करने की अनुमति देता है जो मैं चाहता हूं, यह सिर्फ Django मॉडल के साथ कुछ गलत है। कोई विचार?

संपादित करें

जहां तक ​​डीबी के समाधान की पोर्टेबिलिटी नहीं है, हम पोस्टग्रैज से खुश हैं। मैंने एक कॉल करने योग्य के लिए अद्वितीय स्थापित करने का प्रयास किया है, जो कि बार के विशिष्ट मूल्यों के लिए ट्रू / फाल्स लौटाने का मेरा कार्य था , इसने कोई त्रुटि नहीं दी, हालांकि इस तरह से कोई प्रभाव नहीं पड़ा।

अब तक, मैंने बार प्रॉपर्टी से अद्वितीय स्पेसियर हटा दिया है और एप्लिकेशन में बार विशिष्टता को संभाल रहा है , हालांकि अभी भी अधिक सुरुचिपूर्ण समाधान की तलाश में है। कोई सिफारिशें?


मैं अभी तक यहाँ टिप्पणी नहीं कर सकता हूँ, यहाँ थोड़ा शक्तिशाली है: Django 1.4 के बाद से आपको def get_db_prep_value(self, value, connection, prepared=False)विधि कॉल की आवश्यकता होगी । अधिक informations के लिए group.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ चेक करें । निम्नलिखित विधि मेरे लिए भी काम करती है,: get get_prep_value (स्वयं, मान): if value == "": #if Django '' string को बचाने की कोशिश करता है, db कोई नहीं भेजता (NULL) कोई और नहीं लौटाता: वापसी मान # सही, बस मान पास करें
जेन्स

मैंने इसके लिए एक Django टिकट खोला। अपना समर्थन जोड़ें। code.djangoproject.com/ticket/30210#ticket
कार्ल

जवाबों:


154

टिकट # 9039 तय होने के बाद से Django ने NULL को यूनिकनेस चेक के लिए NULL के बराबर नहीं माना है, देखें:

http://code.djangoproject.com/ticket/9039

यहाँ मुद्दा यह है कि एक फार्म चारफिल्ड के लिए सामान्यीकृत "रिक्त" मान एक खाली स्ट्रिंग है, कोई भी नहीं। इसलिए यदि आप फ़ील्ड को खाली छोड़ देते हैं, तो आपको एक रिक्त स्ट्रिंग मिलती है, न कि NULL, जिसे DB में संग्रहीत किया जाता है। Django और डेटाबेस नियमों दोनों के तहत, खाली तार विशिष्ट जांच के लिए खाली स्ट्रिंग के बराबर हैं।

आप किसी खाली स्ट्रिंग के लिए NULL को संग्रहीत करने के लिए व्यवस्थापक इंटरफ़ेस को बाध्य कर सकते हैं, Foo के लिए अपना स्वयं का अनुकूलित मॉडल फ़ॉर्म प्रदान करके एक clean_bar विधि के साथ जो रिक्त स्ट्रिंग को किसी में नहीं बदलता है:

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

2
यदि बार खाली है, तो इसे प्री_स्वे विधि में से किसी के साथ बदलें। कोड अधिक DRY I मान लिया जाएगा।
आशीष गुप्ता

6
यह उत्तर केवल फॉर्म-आधारित डेटा इनपुट के लिए मदद करता है, लेकिन वास्तव में डेटा अखंडता की रक्षा के लिए कुछ भी नहीं करता है। डेटा को आयात स्क्रिप्ट के माध्यम से, शेल से, एपीआई या किसी अन्य माध्यम से दर्ज किया जा सकता है। डेटा को छूने वाले हर रूप के लिए कस्टम केस बनाने की तुलना में सेव () विधि को ओवरराइड करने के लिए बेहतर है।
शकर

Django 1.9+ को उदाहरणों में fieldsया excludeविशेषता की आवश्यकता होती है ModelForm। आप Metaव्यवस्थापक में उपयोग के लिए ModelForm से आंतरिक वर्ग को छोड़ कर इसके चारों ओर काम कर सकते हैं । संदर्भ: docs.djangoproject.com/en/1.10/ref/contrib/admin/…
user85461

62

** संपादित 11/30/2015 : अजगर 3 में, मॉड्यूल-वैश्विक __metaclass__चर अब समर्थित नहीं है । Additionaly, के रूप में वर्ग था पदावनत :Django 1.10SubfieldBase

से डॉक्स :

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

मुझे लगता है कि व्यवस्थापक में साफ़ किए गए_डेटा को ओवरराइड करने से बेहतर तरीका यह होगा कि आप चरखे को हटा दें - इस तरह कोई फर्क नहीं पड़ता कि मैदान किस रूप में पहुँचता है, यह "बस काम करेगा।" ''डेटाबेस में भेजे जाने से ठीक पहले आप इसे पकड़ सकते हैं , और डेटाबेस से बाहर आने के बाद NULL को पकड़ सकते हैं, और बाकी के Django को पता नहीं चलेगा / परवाह नहीं है। एक त्वरित और गंदा उदाहरण:

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फ़ाइल में रख सकता हूं । फ़ील्ड एक चारफिल्ड की तरह कार्य करता है - बस blank=True, null=Trueक्षेत्र को घोषित करते समय सेट करना याद रखें , या अन्यथा Django एक सत्यापन त्रुटि (फ़ील्ड आवश्यक) फेंक देगा या एक db स्तंभ बना देगा जो NULL को स्वीकार नहीं करता है।


3
get_prep_value में, आपको मूल्य को अलग करना चाहिए, यदि मूल्य में कई स्थान हैं।
ax003d

1
2016 में यहां अपडेट किया गया जवाब Django 1.10 और EmailField का उपयोग करके अच्छी तरह से काम करता है।
k0nG

4
यदि आप एक होने के CharFieldलिए अपडेट कर रहे हैं CharNullField, तो आपको इसे तीन चरणों में करना होगा। सबसे पहले, null=Trueफ़ील्ड में जोड़ें , और माइग्रेट करें। फिर, किसी भी रिक्त मान को अद्यतन करने के लिए डेटा माइग्रेशन करें ताकि वे शून्य हों। अंत में, फ़ील्ड को चारनफिल्ड में बदलें। यदि आप डेटा माइग्रेशन करने से पहले फ़ील्ड को परिवर्तित करते हैं, तो आपका डेटा माइग्रेशन कुछ भी नहीं करेगा।
ग्लीसर

3
ध्यान दें कि अद्यतन समाधान में, from_db_value()उस अतिरिक्त contexपैरामीटर नहीं होना चाहिए । यह होना चाहिएdef from_db_value(self, value, expression, connection):
फिल गिफोर्ड

1
@PhilGyford की टिप्पणी 2.0 के रूप में लागू होती है।
शहीद हक़

16

क्योंकि मैं स्टैकओवरफ़्लो के लिए नया हूं इसलिए मुझे अभी तक जवाब देने की अनुमति नहीं है, लेकिन मैं यह कहना चाहूंगा कि दार्शनिक दृष्टिकोण से, मैं इस प्रश्न के सबसे लोकप्रिय उत्तर से सहमत नहीं हो सकता। (करेन ट्रेसी द्वारा)

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

इसलिए, अपने कोड को सही मायने में ओओपी रखने के लिए, आपको अपने फू मॉडल की आंतरिक विधि का उपयोग करना होगा। सहेजें () विधि या फ़ील्ड को संशोधित करना अच्छे विकल्प हैं, लेकिन ऐसा करने के लिए फ़ॉर्म का उपयोग करना निश्चित रूप से नहीं है।

व्यक्तिगत रूप से मैं सुझाए गए मॉडल के लिए पोर्टेबिल्डफील्ड का उपयोग करना पसंद करता हूं, जिसे मैं भविष्य में परिभाषित कर सकता हूं।


13

त्वरित सुधार करना है:

def save(self, *args, **kwargs):

    if not self.bar:
        self.bar = None

    super(Foo, self).save(*args, **kwargs)

2
ध्यान रखें कि MyModel.objects.bulk_create()इस विधि का उपयोग करने से बाईपास हो जाएगा।
बेंजामिन

क्या यह तरीका तब कहा जाता है जब हम एडमिन पैनल से बचते हैं? मैंने कोशिश की, लेकिन यह नहीं है।
किशन मेहता

1
@ किशन django- व्यवस्थापक पैनल दुर्भाग्य से इन हुक को छोड़ देगा
विन्सेन्ट Buscarello

@ ई-सतीस आपका तर्क ध्वनि है इसलिए मैंने इसे लागू किया, लेकिन त्रुटि अभी भी एक समस्या है। Im हो रही कहा अशक्त एक डुप्लिकेट है।
विंसेंट बसकेरलो

6

एक और संभव उपाय

class Foo(models.Model):
    value = models.CharField(max_length=255, unique=True)

class Bar(models.Model):
    foo = models.OneToOneField(Foo, null=True)

यह एक अच्छा समाधान नहीं है क्योंकि आप अनावश्यक संबंध बना रहे हैं।
बुरक ir

3

यह अब तय हो गया है कि https://code.djangoproject.com/ticket/4136 हल हो गया है। Django 1.11+ में आप models.CharField(unique=True, null=True, blank=True)रिक्त मानों को मैन्युअल रूप से परिवर्तित किए बिना उपयोग कर सकते हैं None


1

मुझे हाल ही में वही आवश्यकता थी। विभिन्न क्षेत्रों को उपवर्गित करने के बजाय, मैंने अपने मॉडल (नीचे 'MyModel' नाम से) को बचाने के लिए ओवरराइड किया।

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

1

यदि आपके पास एक मॉडल MyModel है और चाहते हैं कि my_field अशक्त या अद्वितीय है, तो आप मॉडल की सेव विधि को ओवरराइड कर सकते हैं:

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)

इस तरह, फ़ील्ड रिक्त नहीं हो सकती है केवल गैर-रिक्त या रिक्त होगी। nulls विरोधाभास विशिष्टता नहीं है


0

बेहतर या बदतर के लिए, Django को विशिष्टता की जाँच के प्रयोजनों के NULLबराबर माना जाता है NULL। वास्तव में इसके चारों ओर कोई ऐसा तरीका नहीं है जो अद्वितीयता जांच के अपने स्वयं के कार्यान्वयन को लिखता हो, जो NULLकि किसी तालिका में कितनी बार हो, अद्वितीय नहीं माना जाता है।

(और ध्यान रखें कि कुछ DB समाधान एक ही विचार रखते हैं NULL, इसलिए एक DB के विचारों पर निर्भर कोड NULLदूसरों के लिए पोर्टेबल नहीं हो सकता है)


6
यह सही उत्तर नहीं है। स्पष्टीकरण के लिए यह उत्तर देखें ।
कार्ल जी

2
इससे सहमत होना सही नहीं है। मैंने अभी अभी Django 1.4 में IntegerField (रिक्त = सत्य, null = True, unique = True) का परीक्षण किया है और यह अशक्त मानों के साथ कई पंक्तियों को अनुमति देता है।
स्लाइस

0

आप इस क्षेत्र को सूची में शामिल नहीं करने UniqueConstraintकी शर्त के साथ जोड़ सकते हैं । यदि आपको आवश्यकता है कि विच मान के साथ बाधा भी नहीं है , तो आप अतिरिक्त जोड़ सकते हैं।nullable_field=nullfieldsnullable_fieldnull

नोट: UniqueConstraint को django 2.2 के बाद से जोड़ा गया था

class Foo(models.Model):
    name = models.CharField(max_length=40)
    bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
    
    class Meta:
        constraints = [
            # For bar == null only
            models.UniqueConstraint(fields=['name'], name='unique__name__when__bar__null',
                                    condition=Q(bar__isnull=True)),
            # For bar != null only
            models.UniqueConstraint(fields=['name', 'bar'], name='unique__name__when__bar__not_null')
        ]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.