2025, Sep 23 13:31
Django में managed=False होने पर constraints और IntegrityError कैसे काम करते हैं
जानें क्यों Django में managed=False होने पर Check/Unique constraints डेटाबेस तक नहीं पहुँचते और save() पर IntegrityError नहीं उठती; समाधान: DB में constraints जोड़ें.
जब Django मॉडल में डेटाबेस constraints परीक्षणों के दौरान IntegrityError उठाने से मना कर दें, तो अक्सर हम Q expressions या टेस्ट सेटअप को दोष देते हैं। लेकिन यदि मॉडल managed = False के साथ घोषित है, तो Django टेबल स्कीमा को छुएगा ही नहीं। ऐसे में, मॉडल पर घोषित constraints कभी डेटाबेस तक पहुंचते ही नहीं, इसलिए डेटाबेस के पास लागू करने के लिए कुछ होता ही नहीं। नतीजा: चाहे डेटा कितना भी गलत हो, save() पर कोई exception नहीं उठता।
समस्या की रूपरेखा
ऐसा मॉडल मान लें जिसमें कई constraints घोषित हैं—जैसे status को सीमित करने के लिए CheckConstraint और एक नियम जो Draft या Discontinued स्थिति में प्रोडक्ट होने पर date_published सेट करने से रोकता है। मॉडल में कुछ UniqueConstraint घोषणाएँ भी हैं। टेस्ट इन नियमों का उल्लंघन करके IntegrityError ट्रिगर करने की कोशिश करते हैं।
from django.db import models
from django.db.models import CheckConstraint, Q, UniqueConstraint
class CatalogItem(models.Model):
    code = models.CharField(primary_key=True, max_length=8)
    ean = models.CharField(unique=True, max_length=14, blank=True, null=True)
    title = models.TextField(blank=True, null=True)
    msrp = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
    state = models.TextField()
    maker = models.TextField(blank=True, null=True)
    label = models.TextField(blank=True, null=True)
    origin = models.TextField(blank=True, null=True)
    updated_at = models.DateField(blank=True, null=True)
    published_at = models.DateField(blank=True, null=True)
    is_exclusive = models.BooleanField(blank=True, null=True)
    class Meta:
        managed = False
        db_table = 'product'
        constraints = [
            CheckConstraint(
                condition=Q(state__in=['Draft', 'Live', 'Discontinued']),
                name='ck_state_allowed',
                violation_error_message="status field must be on of the following: 'Draft', 'Live', 'Discontinued'",
            ),
            CheckConstraint(
                condition=~(Q(published_at__isnull=False) & Q(state__in=['Draft', 'Discontinued'])),
                name='ck_published_vs_state',
                violation_error_message="Product with status 'Draft' or 'Discontinued' cannot have a date_published value",
            ),
            UniqueConstraint(fields=['ean'], name='uq_ean'),
            UniqueConstraint(fields=['code', 'is_exclusive'], name='uq_code_exclusive'),
            UniqueConstraint(fields=['code', 'state'], name='uq_code_state'),
        ]
एक परीक्षण अमान्य state मान सहेजने की कोशिश करता है और IntegrityError की अपेक्षा करता है:
from django.db.utils import IntegrityError
from django.test import TestCase
from .models import CatalogItem
class CatalogItemTests(TestCase):
    def setUp(self):
        self.item = CatalogItem.objects.create(
            code='12345678',
            ean='5666777888999',
            title='Test Product 1',
            msrp=999,
            state='Live',
            maker='',
            label='Test Brand',
            origin='China',
            updated_at='2025-01-01',
            published_at='2025-01-01',
            is_exclusive=False,
        )
    def test_invalid_state_rejected(self):
        self.item.state = 'WrongStatus'
        with self.assertRaises(IntegrityError):
            self.item.save()
असल में क्या हो रहा है
Django में, Meta.constraints में घोषित constraints का प्रवर्तन डेटाबेस करता है। Django एक CheckConstraint को डेटाबेस-स्तरीय CHECK में बदल देता है, जैसे CHECK status IN ('Draft', 'Live', 'Discontinued'). यह डेटाबेस तय करता है कि कोई पंक्ति डाली या बदली जा सकती है या नहीं; अगर नहीं, तो वह एक integrity error उठाता है, जिसे Django django.db.utils.IntegrityError के रूप में दिखाता है।
लेकिन managed = False तस्वीर पूरी तरह बदल देता है। managed बंद होने पर Django आधारभूत टेबल या उसके किसी भी constraint को न तो बनाता है, न बदलता है, न हटाता है। वह नए फील्ड्स नहीं जोड़ेगा, न CHECK, न UNIQUE। नतीजतन, मॉडल में सूचीबद्ध constraints डेटाबेस में मौजूद ही नहीं होते, इसलिए वे ट्रिगर भी नहीं हो सकते।
समाधान
यदि मॉडल unmanaged है, तो Django द्वारा घोषित वे constraints डेटाबेस स्कीमा पर लागू नहीं होंगे। उन्हें प्रभावी बनाने के लिए, बराबर के constraints सीधे डेटाबेस में जोड़ें। उदाहरण के लिए, status पर प्रतिबंध डेटाबेस-स्तर का मूल CHECK constraint बन जाएगा, जो SQL में कुछ इस तरह दिखेगा:
CHECK (status IN ('Draft', 'Live', 'Discontinued'))
बाकी constraints पर भी यही बात लागू होती है: उन्हें सीधे डेटाबेस में लागू करें, ताकि PostgreSQL 14.18 अमान्य पंक्तियों को अस्वीकार कर सके और integrity error उठा सके।
जब अनुबंध का मालिक डेटाबेस हो, तो save() उल्लंघनों को IntegrityError के रूप में आगे बढ़ाएगा, और जिन परीक्षणों को इन उल्लंघनों की अपेक्षा है, वे उनके प्रवर्तन के लिए डेटाबेस पर भरोसा कर सकते हैं।
यह क्यों मायने रखता है
किसी constraint की ताकत उसके प्रवर्तन बिंदु जितनी ही होती है। Django में Meta.constraints इरादे को कोड के रूप में बांधता है और, जब managed तालिकाएँ इस्तेमाल हो रही हों, तो माइग्रेशनों के जरिए वे नियम डेटाबेस में स्थापित करवाता है। managed बंद होने पर यह कड़ी टूट जाती है। कोड अब भी constraints का एलान करता रहता है, पर डेटाबेस उनसे अनजान रहता है—इसलिए लिखते समय कुछ विफल नहीं होता। इस सीमा को समझना परीक्षण और प्रोडक्शन में गलत धारणाओं से बचाता है।
मुख्य बातें
Django में घोषित डेटाबेस constraints का प्रवर्तन डेटाबेस करता है, न कि save() में Python कोड। managed = False होने पर Django उन constraints को टेबल के साथ समकालित नहीं करेगा, इसलिए वे न तो टेस्ट में, न ही प्रोडक्शन में ट्रिगर होंगे। इन नियमों से IntegrityError पर भरोसा करना हो, तो संबंधित constraints सीधे डेटाबेस में जोड़ें—वहीं जहां उनका वास्तविक प्रवर्तन होता है।
यह लेख StackOverflow के प्रश्न (लेखक: dabo_tusev) और willeM_ Van Onsem के उत्तर पर आधारित है।