Django में - मॉडल वंशानुक्रम - क्या यह आपको मूल मॉडल की विशेषता को ओवरराइड करने की अनुमति देता है?


99

मैं यह करने के लिए देख रहा हूँ:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)

यह वह संस्करण है जिसका मैं उपयोग करना चाहूँगा (हालाँकि मैं किसी भी सुझाव के लिए खुला हूँ): http://docs.djangoproject.com/en/dev/topics/db/models/#id7

क्या यह Django में समर्थित है? यदि नहीं, तो क्या समान परिणाम प्राप्त करने का कोई तरीका है?


क्या आप कृपया उत्तर को स्वीकार कर सकते हैं bellow, django 1.10 से यह संभव है :)
Holms

@ होम्स केवल अगर बेस क्लास सार है!
मीका वाल्टर

जवाबों:


64

अपडेट किया गया उत्तर: जैसा कि लोगों ने टिप्पणियों में उल्लेख किया है, मूल उत्तर प्रश्न का ठीक से उत्तर नहीं दे रहा था। वास्तव में, केवल LongNamedRestaurantमॉडल डेटाबेस में बनाया Placeगया था , नहीं था।

एक समाधान "प्लेस", उदाहरण के लिए एक सार मॉडल बनाने के लिए है। AbstractPlace, और इससे विरासत:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)

कृपया @Mark उत्तर भी पढ़ें , वह एक महान व्याख्या देता है कि आप गैर-सार वर्ग से विरासत में मिली विशेषताओं को क्यों नहीं बदल सकते हैं।

(ध्यान दें कि यह केवल Django 1.10 के बाद से संभव है: Django 1.10 से पहले, एक विशेष वर्ग से विरासत में मिली विशेषता को संशोधित करना संभव है।)

मूल उत्तर

Django 1.10 के बाद से यह संभव है ! आपको बस वही करना है जो आपने माँगा था:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)

8
जगह अमूर्त होने की जरूरत है, नहीं?
DylanYoung

4
मुझे नहीं लगता कि मैंने एक अलग प्रश्न का उत्तर दिया क्योंकि मैं अभी कह रहा हूं कि प्रश्न में पोस्ट किया गया कोड अब Django 1.10 के बाद से काम कर रहा है। ध्यान दें कि वह जिस लिंक का उपयोग करना चाहता था, उसके लिंक के अनुसार, वह प्लेस क्लास को अमूर्त बनाना भूल गया है।
qmarlats

2
निश्चित नहीं कि यह स्वीकृत उत्तर क्यों है ... ओपी मल्टी-टेबल इनहेरिटेंस का उपयोग कर रहा है। यह उत्तर केवल अमूर्त आधार वर्गों के लिए मान्य है।
श्रीनाम

1
Django 1.10 से पहले सार कक्षाएं उपलब्ध थीं
rbennell

1
@NoamG मेरे मूल उत्तर में, Placeसार था, इसलिए इसे डेटाबेस में नहीं बनाया गया था । लेकिन ओ पी चाहता था दोनों Placeऔर LongNamedRestaurantडेटाबेस में बनाया जाना। इसलिए मैंने AbstractPlaceमॉडल को जोड़ने के लिए अपने उत्तर को अपडेट किया , जो "आधार" (यानी। सार) मॉडल है Placeऔर दोनों LongNamedRestaurantसे विरासत में मिला है। अब दोनों Placeऔर LongNamedRestaurant, डेटाबेस में बनाए जाते हैं के रूप में ओ पी के लिए कहा।
क़मरलैट्स

61

नहीं, यह नहीं है :

फ़ील्ड नाम "छिपाना" की अनुमति नहीं है

सामान्य पायथन क्लास इनहेरिटेंस में, चाइल्ड क्लास के लिए पेरेंट क्लास की किसी भी विशेषता को ओवरराइड करने की अनुमति है। Django में, यह उन विशेषताओं के लिए अनुमत नहीं है जो Fieldउदाहरण हैं (कम से कम, फिलहाल नहीं)। यदि किसी बेस क्लास के पास एक फ़ील्ड है author, तो आप authorउस बेस क्लास से विरासत में मिली किसी भी क्लास में एक और मॉडल फ़ील्ड नहीं बना सकते हैं ।


11
यह असंभव क्यों है, इसके लिए मेरा जवाब देखें। लोग इसे पसंद करते हैं क्योंकि इसका कोई मतलब नहीं है, यह तुरंत स्पष्ट नहीं है।
मार्क

4
@ leo-the-manic मुझे लगता है कि User._meta.get_field('email').required = Trueकाम कर सकता है, निश्चित नहीं सोचा था।
जेन्स टिम्मरमैन

@ leo-a-manic, @JensTimmerman, @utapyngo अपने वर्ग के संपत्ति मूल्य को निर्धारित करने से विरासत में मिले क्षेत्रों पर प्रभाव नहीं पड़ेगा। आपको _metaअभिभावक वर्ग पर कार्य करना होगा , उदाहरण के लिए MyParentClass._meta.get_field('email').blank = False( emailव्यवस्थापन में विरासत में अनिवार्य क्षेत्र बनाने के लिए )
पीटरिनो

1
ओह, क्षमा करें, @ utapyngo का कोड ऊपर सही है, लेकिन इसे वर्ग निकाय के बाहर रखा जाना चाहिए , बाद में! माता-पिता वर्ग के क्षेत्र को निर्धारित करने के रूप में मैंने सुझाव दिया है कि अवांछित दुष्प्रभाव हो सकते हैं।
पीटरिनो सेप

मैं चाहता हूं कि प्रत्येक उपवर्ग में एक क्षेत्र एक अलग प्रकार का हो, जिसमें एक ही नाम हो, जो अमूर्त अभिभावक वर्ग में एक ही नाम हो, ताकि सभी उपवर्गों में एक निश्चित नाम के साथ एक क्षेत्र हो। utapyngo का कोड इस आवश्यकता को पूरा नहीं करता है।
डैनियल

28

यह तब तक संभव नहीं है जब तक कि अमूर्त नहीं है, और यहाँ क्यों है: LongNamedRestaurantएक भी है Place, न केवल एक वर्ग के रूप में, बल्कि डेटाबेस में भी। स्थान-तालिका में प्रत्येक शुद्ध Placeऔर प्रत्येक के लिए एक प्रविष्टि है LongNamedRestaurantLongNamedRestaurantबस के साथ एक अतिरिक्त तालिका बनाता हैfood_type जगह के और स्थान तालिका का संदर्भ देता है।

यदि आप करते हैं Place.objects.all(), तो आपको हर वह स्थान भी मिलता है जो एक है LongNamedRestaurant, और यह Place(बिना food_type) का एक उदाहरण होगा । इसलिए Place.nameऔर LongNamedRestaurant.nameसमान डेटाबेस कॉलम साझा करें, और इसलिए उसी प्रकार का होना चाहिए।

मुझे लगता है कि यह सामान्य मॉडल के लिए समझ में आता है: हर रेस्तरां एक जगह है, और उस जगह पर कम से कम सब कुछ होना चाहिए। शायद यह स्थिरता भी है कि 1.10 से पहले अमूर्त मॉडल के लिए यह क्यों संभव नहीं था, हालांकि यह डेटाबेस की समस्याओं को नहीं देगा। @Lampslave की टिप्पणी के अनुसार, इसे 1.10 में संभव बनाया गया था। मैं व्यक्तिगत रूप से देखभाल की सलाह दूंगा: यदि Sub.x Super.x को ओवरराइड करता है, तो सुनिश्चित करें कि Sub.x Super.x का उपवर्ग है, अन्यथा Super के स्थान पर Sub का उपयोग नहीं किया जा सकता।

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


मुझे लगता है कि यह 1.10 में परिवर्तन के कारण है: "सार आधार वर्गों से विरासत में मिली ओवरराइडिंग मॉडल फ़ील्ड।" docs.djangoproject.com/en/2.0/releases/1.10/#models
लैंपस्लेव

मुझे संदेह है क्योंकि यह अभी तक बाहर नहीं था, लेकिन यह एक अच्छी बात है, धन्यवाद!
मार्क

19

Https://stackoverflow.com/a/6379556/15690 देखें :

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True

2
विशेषता: विशेषता सेट नहीं कर सकता (और (लेकिन मैं सेट के विकल्प की कोशिश कर रहा हूं
अलेक्सई

यह Django 1.11 पर काम नहीं करता है (यह पिछले संस्करणों पर काम करता था) ... स्वीकृत प्रतिक्रिया काम करती है
Acaruci

9

अपने कोड को एक ताज़ा ऐप में शामिल किया, INSTALLED_APPS में ऐप जोड़ा और सिंकडाउन चलाया:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'

लगता है कि Django का समर्थन नहीं करता है।


7

कोड का यह सुपरकोल टुकड़ा आपको अमूर्त अभिभावक वर्गों में 'ओवरराइड' करने की अनुमति देता है।

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")

जब खेतों को मूल माता-पिता वर्ग से हटा दिया गया है तो आप उन्हें आवश्यकतानुसार पुनर्परिभाषित करने के लिए स्वतंत्र हैं।

यह मेरा अपना काम नहीं है। मूल कोड यहाँ से: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba


6

हो सकता है कि आप योगदान_ के साथ सौदा कर सकें:

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')

Syncdb ठीक काम करता है। मैंने इस उदाहरण की कोशिश नहीं की, मेरे मामले में मैं बस एक बाधा पैरामीटर को ओवरराइड करता हूं ... प्रतीक्षा करें और देखें!


1
योगदान करने के लिए भी तर्क_to_class अजीब लगते हैं (चारों ओर गलत तरीका भी?) लगता है जैसे आपने इसे स्मृति से टाइप किया था। क्या आप अपने द्वारा परीक्षण किए गए वास्तविक कोड की आपूर्ति कर सकते हैं? यदि आपको यह काम करने के लिए मिला है, तो मुझे यह जानना अच्छा लगेगा कि आपने यह कैसे किया।
माइकल बाइल्स्ट्रा

यह मेरे लिए काम नहीं कर रहा है। एक काम करने के उदाहरण में भी रुचि होगी।
गरोमार्क

कृपया ब्लॉग देखें। jupo.org/2011/11/10/django-model-field-injection यह योगदान होना चाहिए_to_class (<ModelClass>, <fieldToReplace>)
goh

3
Place._meta.get_field('name').max_length = 255कक्षा में शरीर को बिना ओवरराइड किए, चाल चलनी चाहिए __init__()। अधिक संक्षिप्त भी होगा।
पीटरिनो

4

मुझे पता है कि यह एक पुराना सवाल है, लेकिन मुझे एक समान समस्या थी और एक वर्कअराउंड मिला:

मेरे पास निम्न वर्ग थे:

class CommonInfo(models.Model):
    image = models.ImageField(blank=True, null=True, default="")

    class Meta:
        abstract = True

class Year(CommonInfo):
    year = models.IntegerField() 

लेकिन मैं चाहता था कि सुपरक्लास की छवि क्षेत्र को अशक्त बनाये रखते हुए वर्ष की विरासत वाली छवि-क्षेत्र की आवश्यकता हो। अंत में मैंने सत्यापन चरण में छवि को लागू करने के लिए ModelForms का उपयोग किया:

class YearForm(ModelForm):
    class Meta:
        model = Year

    def clean(self):
        if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
            raise ValidationError("Please provide an image.")

        return self.cleaned_data

admin.py:

class YearAdmin(admin.ModelAdmin):
    form = YearForm

ऐसा लगता है कि यह केवल कुछ स्थितियों के लिए लागू है (निश्चित रूप से जहां आपको उपवर्ग क्षेत्र पर कड़े नियमों को लागू करने की आवश्यकता है)।

वैकल्पिक रूप से आप clean_<fieldname>()इसके बजाय विधि का उपयोग कर सकते हैं clean(), उदाहरण के लिए यदि किसी फ़ील्ड townको भरना आवश्यक है:

def clean_town(self):
    town = self.cleaned_data["town"]
    if not town or len(town) == 0:
        raise forms.ValidationError("Please enter a town")
    return town

1

आप मॉडल फ़ील्ड को ओवरराइड नहीं कर सकते, लेकिन इसकी आसानी से स्वच्छ () पद्धति को ओवरराइड / निर्दिष्ट करके प्राप्त किया जा सकता है। मेरे पास ईमेल क्षेत्र के साथ समस्या थी और मैं इसे आदर्श स्तर पर अद्वितीय बनाना चाहता था और इसे इस तरह किया:

def clean(self):
    """
    Make sure that email field is unique
    """
    if MyUser.objects.filter(email=self.email):
        raise ValidationError({'email': _('This email is already in use')})

त्रुटि संदेश फिर "ईमेल" नाम के साथ फॉर्म फ़ील्ड द्वारा कैप्चर किया जाता है


सवाल एक चर क्षेत्र के max_length का विस्तार करने के बारे में है। यदि इसे डेटाबेस द्वारा लागू किया जाता है, तो यह "समाधान" मदद नहीं करता है। बेस मॉडल में लंबे समय तक अधिकतम गति को निर्दिष्ट करने और वहां की कम लंबाई को लागू करने के लिए स्वच्छ () पद्धति का उपयोग करने के लिए एक वर्कअराउंड होगा।
DylanYoung

0

मेरा समाधान आगे के रूप में सरल है monkey patching, ध्यान दें कि मैंने मॉडल में फ़ील्ड के max_lengthलिए विशेषता कैसे बदल दी है :nameLongNamedRestaurant

class Place(models.Model):
   name = models.CharField(max_length=20)

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.