Django 2.1 - Writing custom model fields

कस्टम मॉडल फ़ील्ड लिखना




django

कस्टम मॉडल फ़ील्ड लिखना

परिचय

मॉडल संदर्भ प्रलेखन बताता है कि कैसे Django के मानक क्षेत्र की कक्षाओं का उपयोग करें - CharField , DateField , आदि। कई उद्देश्यों के लिए, उन वर्गों को आप सभी की आवश्यकता होगी कभी-कभी, हालांकि, Django संस्करण आपकी सटीक आवश्यकताओं को पूरा नहीं करेगा, या आप एक ऐसे क्षेत्र का उपयोग करना चाहते हैं जो Django के साथ भेजे गए लोगों से पूरी तरह से अलग है।

Django के अंतर्निहित फ़ील्ड प्रकार हर संभव डेटाबेस कॉलम प्रकार को कवर नहीं करते हैं - केवल सामान्य प्रकार, जैसे VARCHAR और INTEGER । अधिक अस्पष्ट स्तंभ प्रकारों के लिए, जैसे कि भौगोलिक बहुभुज या यहां तक ​​कि उपयोगकर्ता-निर्मित प्रकार जैसे कि PostgreSQL कस्टम प्रकार , आप अपने स्वयं के Django Field उपवर्गों को परिभाषित कर सकते हैं।

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

हमारे उदाहरण वस्तु

कस्टम फ़ील्ड बनाने के लिए विस्तार पर थोड़ा ध्यान देने की आवश्यकता होती है। चीजों को आसान बनाने के लिए, हम इस दस्तावेज़ में एक सुसंगत उदाहरण का उपयोग करेंगे: Bridge एक हाथ में कार्ड के सौदे का प्रतिनिधित्व करने वाले पायथन ऑब्जेक्ट को लपेटना। चिंता न करें, आपको यह जानने की ज़रूरत नहीं है कि इस उदाहरण का पालन करने के लिए ब्रिज कैसे खेलें। आपको केवल यह जानने की आवश्यकता है कि 52 कार्ड चार खिलाड़ियों के लिए समान रूप से निपटाए गए हैं, जिन्हें पारंपरिक रूप से उत्तर , पूर्व , दक्षिण और पश्चिम कहा जाता है। हमारी कक्षा कुछ इस तरह दिखती है:

class Hand:
    """A hand of cards (bridge style)"""

    def __init__(self, north, east, south, west):
        # Input parameters are lists of cards ('Ah', '9s', etc.)
        self.north = north
        self.east = east
        self.south = south
        self.west = west

    # ... (other possibly useful methods omitted) ...

यह सिर्फ एक साधारण पायथन वर्ग है, जिसमें कुछ भी नहीं है, इसके बारे में Django- विशिष्ट नहीं है। हम अपने मॉडल में इस तरह की चीजें करने में सक्षम होना चाहते हैं (हम मानते हैं कि मॉडल पर hand विशेषता hand का एक उदाहरण है):

example = MyModel.objects.get(pk=1)
print(example.hand.north)

new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()

हम अपने मॉडल में किसी भी अन्य पायथन वर्ग की तरह ही hand विशेषता को असाइन और पुनः प्राप्त करते हैं। चाल में Django को यह बताने के लिए है कि इस तरह के ऑब्जेक्ट को सहेजने और लोड करने का तरीका कैसे संभालना है।

अपने मॉडल में Hand क्लास का उपयोग करने के लिए, हमें इस क्लास को बिल्कुल भी बदलने की आवश्यकता नहीं है। यह आदर्श है, क्योंकि इसका मतलब है कि आप आसानी से मौजूदा कक्षाओं के लिए मॉडल समर्थन लिख सकते हैं जहां आप स्रोत कोड को बदल नहीं सकते हैं।

ध्यान दें

आप केवल कस्टम डेटाबेस कॉलम प्रकारों का लाभ उठाना चाहते हैं और अपने मॉडल में मानक पायथन प्रकारों के रूप में डेटा के साथ सौदा कर सकते हैं; उदाहरण के लिए तार, या तैरता है। यह मामला हमारे Hand उदाहरण के समान है और हम साथ जाते ही किसी भी अंतर को नोट कर लेंगे।

पृष्ठभूमि सिद्धांत

डेटाबेस संग्रहण

एक मॉडल क्षेत्र के बारे में सोचने का सबसे सरल तरीका यह है कि यह एक सामान्य पायथन ऑब्जेक्ट - स्ट्रिंग, बूलियन, datetime , या Hand तरह कुछ और जटिल लेने का एक तरीका प्रदान करता है - और इसे एक प्रारूप से और उस प्रारूप में परिवर्तित करें जो व्यवहार करते समय उपयोगी होता है डेटाबेस (और क्रमांकन, लेकिन, जैसा कि हम बाद में देखेंगे, कि डेटाबेस के नियंत्रण में एक बार स्वाभाविक रूप से बाहर गिर जाता है)।

किसी मॉडल में फ़ील्ड्स को किसी मौजूदा डेटाबेस कॉलम प्रकार में फिट होने के लिए परिवर्तित किया जाना चाहिए। अलग-अलग डेटाबेस वैध कॉलम प्रकारों के विभिन्न सेट प्रदान करते हैं, लेकिन नियम अभी भी समान है: वे एकमात्र प्रकार हैं जिनके साथ आपको काम करना है। डेटाबेस में आप जो कुछ भी स्टोर करना चाहते हैं, उनमें से एक प्रकार में फिट होना चाहिए।

आमतौर पर, आप या तो एक विशेष डेटाबेस कॉलम प्रकार से मेल करने के लिए एक Django फ़ील्ड लिख रहे हैं, या एक स्ट्रिंग के लिए अपने डेटा को परिवर्तित करने के लिए एक बिल्कुल सीधा तरीका है।

हमारे Hand उदाहरण के लिए, हम पूर्व-निर्धारित क्रम में सभी कार्डों को एक साथ जोड़कर कार्ड डेटा को १०४ अक्षरों की एक स्ट्रिंग में बदल सकते हैं - कहते हैं, सभी उत्तर कार्ड पहले, फिर पूर्व , दक्षिण और पश्चिम कार्ड। इसलिए Hand ऑब्जेक्ट्स को डेटाबेस में टेक्स्ट या कैरेक्टर कॉलम में सेव किया जा सकता है।

एक फील्ड क्लास क्या करता है?

Django के सभी फ़ील्ड (और जब हम इस दस्तावेज़ में फ़ील्ड कहते हैं , तो हमारा मतलब हमेशा मॉडल फ़ील्ड होता है और फ़ील्ड नहीं django.db.models.Field ) django.db.models.Field उपवर्ग होते हैं। एक क्षेत्र के बारे में जो जानकारी Django रिकॉर्ड करती है, उसमें से अधिकांश सभी फ़ील्डों के लिए आम है - नाम, मदद पाठ, विशिष्टता और आगे। वह सभी जानकारी संग्रहीत करके Field द्वारा नियंत्रित किया जाता है। हम बाद में Field क्या कर सकते हैं, इसके सटीक विवरण में मिलेंगे; अभी के लिए, यह कहना कि सब कुछ Field से उतरता है और फिर वर्ग व्यवहार के प्रमुख टुकड़ों को अनुकूलित करता है।

यह महसूस करना महत्वपूर्ण है कि एक Django फ़ील्ड क्लास वह नहीं है जो आपके मॉडल विशेषताओं में संग्रहीत है। मॉडल विशेषताओं में सामान्य पायथन ऑब्जेक्ट शामिल हैं। मॉडल वर्ग में आपके द्वारा परिभाषित फ़ील्ड कक्षाएं वास्तव में Meta क्लास में संग्रहीत की जाती हैं जब मॉडल वर्ग बनाया जाता है (यह कैसे किया जाता है इसका सटीक विवरण यहां महत्वहीन है)। इसका कारण यह है कि जब आप केवल विशेषताएँ बना और संशोधित कर रहे हैं तो फ़ील्ड कक्षाएं आवश्यक नहीं हैं। इसके बजाय, वे मशीनरी को विशेषता मान के बीच परिवर्तित करने के लिए प्रदान करते हैं और डेटाबेस में क्या संग्रहीत किया जाता है या serializer को भेजा जाता है।

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

  • पहला वर्ग पायथन ऑब्जेक्ट है जिसे आपके उपयोगकर्ता हेरफेर करेंगे। वे इसे मॉडल विशेषता को सौंपेंगे, वे इसे उद्देश्यों को प्रदर्शित करने के लिए पढ़ेंगे, जैसी चीजें। यह हमारे उदाहरण में Hand कक्षा है।
  • दूसरा वर्ग Field उपवर्ग है। यह वह वर्ग है जो जानता है कि अपने स्थायी भंडारण फॉर्म और पायथन फॉर्म के बीच अपने पहले वर्ग को आगे और पीछे कैसे परिवर्तित किया जाए।

फ़ील्ड उपवर्ग लिखना

अपने django.db.models.Field उपवर्ग की योजना बनाते समय, पहले कुछ विचार दें कि आपका नया django.db.models.Field किस मौजूदा वर्ग के समान है। क्या आप एक मौजूदा Django फ़ील्ड को उप-वर्ग कर सकते हैं और अपने आप को कुछ काम बचा सकते हैं? यदि नहीं, तो आपको django.db.models.Field वर्ग को उप-वर्ग करना चाहिए, जिसमें से सब कुछ नीचे उतरा है।

अपने नए क्षेत्र को प्रारंभ करना, किसी भी तर्क को अलग करने का मामला है जो आपके मामले के लिए सामान्य तर्कों से अलग है और उत्तरार्द्ध को __init__() django.db.models.Field विधि (या आपके मूल वर्ग) से गुजर रहा है।

हमारे उदाहरण में, हम अपने क्षेत्र को HandField । (अपने django.db.models.Field उपवर्ग <Something>Field को कॉल करना एक अच्छा विचार है, इसलिए यह django.db.models.Field उपवर्ग के रूप में आसानी से पहचाना जा सकता है।) यह किसी भी मौजूदा फ़ील्ड की तरह व्यवहार नहीं करता है, इसलिए हम सीधे django.db.models.Field से उपवर्ग करेंगे। "

from django.db import models

class HandField(models.Field):

    description = "A hand of cards (bridge style)"

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__init__(*args, **kwargs)

हमारे HandField अधिकांश मानक क्षेत्र विकल्पों को स्वीकार करते हैं (नीचे दी गई सूची देखें), लेकिन हम यह सुनिश्चित करते हैं कि इसकी एक निश्चित लंबाई हो, क्योंकि इसके लिए केवल 52 कार्ड वैल्यू और उनके सूट रखने की आवश्यकता होती है; कुल 104 पात्र।

ध्यान दें

Django के कई मॉडल क्षेत्र उन विकल्पों को स्वीकार करते हैं जिनके साथ वे कुछ भी नहीं करते हैं। उदाहरण के लिए, आप auto_now दोनों editable और auto_now पास कर सकते हैं और यह editable पैरामीटर ( auto_now सेट होने का अर्थ editable=False ) को अनदेखा कर देगा। इस मामले में कोई त्रुटि नहीं उठाई गई है।

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

Field.__init__() विधि निम्नलिखित पैरामीटर लेती है:

  • verbose_name
  • name
  • primary_key
  • max_length
  • unique
  • blank
  • null
  • db_index
  • rel : संबंधित क्षेत्रों (जैसे ForeignKey ) के लिए उपयोग किया जाता है। केवल उन्नत उपयोग के लिए।
  • default
  • editable
  • serialize : यदि False , तो क्षेत्र को क्रमबद्ध नहीं किया जाएगा जब मॉडल Django के serializer लिए पारित हो। True अवहेलना।
  • unique_for_date
  • unique_for_month
  • unique_for_year
  • choices
  • help_text
  • db_column
  • db_tablespace : केवल इंडेक्स निर्माण के लिए, यदि बैकएंड tablespaces का समर्थन करता है। आप आमतौर पर इस विकल्प को अनदेखा कर सकते हैं।
  • auto_created : True यदि फ़ील्ड स्वचालित रूप से बनाया गया था, जैसा कि मॉडल वंशानुक्रम द्वारा उपयोग किए जाने वाले OneToOneField लिए है। केवल उन्नत उपयोग के लिए।

उपरोक्त सूची में स्पष्टीकरण के बिना सभी विकल्पों का एक ही अर्थ है कि वे सामान्य Django फ़ील्ड्स के लिए करते हैं। उदाहरण और विवरण के लिए फ़ील्ड प्रलेखन देखें।

क्षेत्र का पुनर्निर्माण

आपके __init__() विधि को लिखने का __init__() विधि लिख रहा है। यह विधि Django को बताती है कि अपने नए क्षेत्र का एक उदाहरण कैसे लें और इसे एक क्रमबद्ध रूप में कम करें - विशेष रूप से, इसे फिर से बनाने के लिए __init__() पास जाने के लिए क्या तर्क हैं।

यदि आपने अपने द्वारा विरासत में प्राप्त किए गए क्षेत्र के शीर्ष पर कोई अतिरिक्त विकल्प नहीं जोड़ा है, तो एक नई deconstruct() विधि लिखने की आवश्यकता नहीं है। यदि, हालांकि, आप __init__() में पारित तर्कों को बदल रहे हैं (जैसे हम HandField ), आपको पास किए जा रहे मूल्यों को पूरक करने की आवश्यकता होगी।

deconstruct() का अनुबंध सरल है; यह चार वस्तुओं का एक टपल देता है: फ़ील्ड का विशेषता नाम, फ़ील्ड वर्ग का पूर्ण आयात पथ, स्थिति संबंधी तर्क (सूची के रूप में), और कीवर्ड तर्क (एक तानाशाही के रूप में)। ध्यान दें कि यह कस्टम वर्गों के लिए deconstruct() पद्धति से अलग है जो तीन चीजों का एक टपल देता है।

एक कस्टम फ़ील्ड लेखक के रूप में, आपको पहले दो मानों की परवाह करने की आवश्यकता नहीं है; बेस Field क्लास के पास फ़ील्ड के विशेषता नाम और आयात पथ का पता लगाने के लिए सभी कोड होते हैं। हालाँकि, आपको स्थिति और कीवर्ड के तर्कों के बारे में ध्यान रखना होगा, क्योंकि ये संभावना है कि आप जो चीजें बदल रहे हैं।

उदाहरण के लिए, हमारे HandField क्लास में हम हमेशा __init__() में जबरन __init__() । बेस Field पर deconstruct() विधि इसे देखेगा और इसे कीवर्ड तर्क में वापस करने का प्रयास करेगा; इस प्रकार, हम इसे पठनीयता के लिए कीवर्ड तर्क से छोड़ सकते हैं:

from django.db import models

class HandField(models.Field):

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        del kwargs["max_length"]
        return name, path, args, kwargs

यदि आप एक नया कीवर्ड तर्क जोड़ते हैं, तो आपको अपने मूल्य को स्वयं kwargs में रखने के लिए कोड लिखना होगा:

from django.db import models

class CommaSepField(models.Field):
    "Implements comma-separated storage of lists"

    def __init__(self, separator=",", *args, **kwargs):
        self.separator = separator
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        # Only include kwarg if it's not the default
        if self.separator != ",":
            kwargs['separator'] = self.separator
        return name, path, args, kwargs

अधिक जटिल उदाहरण इस दस्तावेज़ के दायरे से परे हैं, लेकिन याद रखें - आपके फील्ड उदाहरण के किसी भी कॉन्फ़िगरेशन के लिए, deconstruct() को उन दलीलों को वापस करना होगा जिन्हें आप __init__ को उस राज्य को फिर से बनाने के लिए पास कर सकते हैं।

यदि आप Field सुपरक्लास में तर्कों के लिए नए डिफ़ॉल्ट मान सेट करते हैं, तो अतिरिक्त ध्यान दें; यदि आप पुराने डिफ़ॉल्ट मान पर ले जाते हैं, तो यह सुनिश्चित करने के बजाय कि वे हमेशा शामिल हैं, गायब होना चाहते हैं।

इसके अलावा, स्थिति संबंधी तर्कों के रूप में वापसी मूल्यों से बचने की कोशिश करें; जहां संभव हो, अधिकतम भविष्य की अनुकूलता के लिए कीवर्ड तर्क के रूप में मान लौटाएं। बेशक, यदि आप निर्माता की तर्क सूची में उनकी स्थिति से अधिक बार चीजों के नाम बदलते हैं, तो आप स्थितीयता पसंद कर सकते हैं, लेकिन यह ध्यान रखें कि लोग आपके क्षेत्र को क्रमिक संस्करण से काफी समय (संभवतः वर्षों) के लिए फिर से संगठित करेंगे। निर्भर करता है कि आपके प्रवास कितने समय तक रहते हैं।

आप क्षेत्र को शामिल करने वाले प्रवासों में देख कर पुनर्निर्माण के परिणाम देख सकते हैं, और आप इकाई परीक्षणों में डिकंस्ट्रक्शन का परीक्षण कर सकते हैं और केवल क्षेत्र का पुनर्निर्माण और पुनर्निर्माण कर सकते हैं:

name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)

एक कस्टम फ़ील्ड का बेस क्लास बदलना

आप कस्टम फ़ील्ड के आधार वर्ग को नहीं बदल सकते क्योंकि Django परिवर्तन का पता नहीं लगाएगा और इसके लिए कोई माइग्रेशन नहीं करेगा। उदाहरण के लिए, यदि आप इसके साथ शुरू करते हैं:

class CustomCharField(models.CharField):
    ...

और फिर तय करें कि आप इसके बजाय TextField का उपयोग करना चाहते हैं, आप इस तरह उपवर्ग को बदल नहीं सकते हैं:

class CustomCharField(models.TextField):
    ...

इसके बजाय, आपको एक नया कस्टम फ़ील्ड वर्ग बनाना होगा और उसे संदर्भित करने के लिए अपने मॉडल अपडेट करने होंगे:

class CustomCharField(models.CharField):
    ...

class CustomTextField(models.TextField):
    ...

खेतों को हटाने के बारे में चर्चा करने के लिए , आपको मूल CustomCharField वर्ग को बनाए रखना चाहिए जब तक कि आपके पास माइग्रेशन हैं जो इसे संदर्भित करते हैं।

अपने कस्टम क्षेत्र का दस्तावेजीकरण

हमेशा की तरह, आपको अपने फ़ील्ड प्रकार का दस्तावेज़ करना चाहिए, ताकि उपयोगकर्ता जान सकें कि यह क्या है। इसके लिए एक डॉकस्ट्रिंग प्रदान करने के अलावा, जो डेवलपर्स के लिए उपयोगी है, आप व्यवस्थापक ऐप के उपयोगकर्ताओं को django.contrib.admindocs एप्लिकेशन के माध्यम से फ़ील्ड प्रकार का संक्षिप्त विवरण देखने की अनुमति भी दे सकते हैं। ऐसा करने के लिए बस अपने कस्टम फ़ील्ड की एक description श्रेणी विशेषता में वर्णनात्मक पाठ प्रदान करें। उपर्युक्त उदाहरण में, admindocs लिए admindocs एप्लिकेशन द्वारा प्रदर्शित विवरण 'कार्ड का एक हाथ (पुल शैली)' होगा।

django.contrib.admindocs डिस्प्ले में, फ़ील्ड विवरण को field.__dict__ साथ field.__dict__ किया जाता है। field.__dict__ जो विवरण को फ़ील्ड के तर्कों को शामिल करने की अनुमति देता है। उदाहरण के लिए, CharField लिए विवरण है:

description = _("String (up to %(max_length)s)")

उपयोगी तरीके

एक बार जब आप अपना django.db.models.Field उपवर्ग बना लेते हैं, तो आप अपने क्षेत्र के व्यवहार के आधार पर कुछ मानक तरीकों को ओवरराइड करने पर विचार कर सकते हैं। नीचे दिए गए तरीकों की सूची महत्व के लगभग घटते क्रम में है, इसलिए ऊपर से शुरू करें।

कस्टम डेटाबेस प्रकार

मान mytype कि आपने एक पोस्टग्रेक्यूएल कस्टम प्रकार बनाया है जिसे mytype कहा जाता है। आप Field को उप-वर्ग कर सकते हैं और db_type() विधि को लागू कर सकते हैं, जैसे:

from django.db import models

class MytypeField(models.Field):
    def db_type(self, connection):
        return 'mytype'

एक बार जब आपके पास MytypeField , तो आप इसे किसी भी मॉडल में उपयोग कर सकते हैं, किसी भी अन्य Field प्रकार की तरह:

class Person(models.Model):
    name = models.CharField(max_length=80)
    something_else = MytypeField()

यदि आप एक डेटाबेस-अज्ञेय अनुप्रयोग बनाने का लक्ष्य रखते हैं, तो आपको डेटाबेस कॉलम के प्रकारों में अंतर के लिए खाता होना चाहिए। उदाहरण के लिए, PostgreSQL में दिनांक / समय स्तंभ प्रकार को timestamp कहा जाता है, जबकि MySQL में उसी कॉलम को डेटाइम कहा जाता है। db_type() विधि में इसे संभालने का सबसे सरल तरीका है connection.settings_dict['ENGINE'] जाँच करना। db_type() connection.settings_dict['ENGINE'] db_type() connection.settings_dict['ENGINE'] विशेषता।

उदाहरण के लिए:

class MyDateField(models.Field):
    def db_type(self, connection):
        if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
            return 'datetime'
        else:
            return 'timestamp'

db_type() और rel_db_type() विधियों को Django द्वारा बुलाया जाता है जब फ्रेमवर्क आपके एप्लिकेशन के लिए क्रिएट CREATE TABLE स्टेटमेंट का निर्माण करता है - अर्थात, जब आप पहली बार अपनी टेबल बनाते हैं। WHERE क्लॉज का निर्माण करते समय विधियों को भी कहा जाता है, जिसमें मॉडल फ़ील्ड शामिल है - अर्थात, जब आप क्वेरी प्राप्त तरीकों का उपयोग करके डेटा प्राप्त करते हैं, जैसे get() , filter() , और exclude() और मॉडल फ़ील्ड को एक तर्क के रूप में शामिल करें। उन्हें किसी अन्य समय पर नहीं बुलाया जाता है, इसलिए यह उपरोक्त उदाहरण में थोड़े जटिल कोड, जैसे कि connection.settings_dict चेक को निष्पादित करने का खर्च उठा सकता है।

कुछ डेटाबेस कॉलम प्रकार पैरामीटर स्वीकार करते हैं, जैसे कि CHAR(25) , जहां पैरामीटर 25 अधिकतम कॉलम लंबाई का प्रतिनिधित्व करता है। अगर इस तरह के मामलों में, यह अधिक लचीला है अगर पैरामीटर को db_type() विधि में हार्ड-कोड होने के बजाय मॉडल में निर्दिष्ट किया गया है। उदाहरण के लिए, यहाँ दिखाए गए CharMaxlength25Field अधिक अर्थ नहीं होगा:

# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
    def db_type(self, connection):
        return 'char(25)'

# In the model:
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

ऐसा करने का बेहतर तरीका यह होगा कि रन समय पर पैरामीटर को निर्दिष्ट किया जा सकता है - यानी, जब क्लास को तत्काल किया जाता है। ऐसा करने के लिए, बस Field.__init__() लागू करें, जैसे:

# This is a much more flexible example.
class BetterCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(*args, **kwargs)

    def db_type(self, connection):
        return 'char(%s)' % self.max_length

# In the model:
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

अंत में, यदि आपके कॉलम को वास्तव में जटिल एसक्यूएल सेटअप की आवश्यकता है, तो db_type() से None वापस न करें। यह Django के SQL निर्माण कोड को इस क्षेत्र पर छोड़ने का कारण बनेगा। आप निश्चित रूप से किसी अन्य तरीके से सही तालिका में कॉलम बनाने के लिए जिम्मेदार हैं, लेकिन इससे आपको Django को रास्ते से हटने के लिए कहने का एक तरीका मिलता है।

rel_db_type() पद्धति को OneToOneField और OneToOneField जैसे क्षेत्रों द्वारा कहा जाता है जो अपने डेटाबेस कॉलम डेटा प्रकारों को निर्धारित करने के लिए किसी अन्य फ़ील्ड को इंगित करते हैं। उदाहरण के लिए, यदि आपके पास UnsignedAutoField , तो आपको उसी डेटा डेटा का उपयोग करने के लिए उस फ़ील्ड को इंगित करने वाली विदेशी कुंजियों की भी आवश्यकता है:

# MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField):
    def db_type(self, connection):
        return 'integer UNSIGNED AUTO_INCREMENT'

    def rel_db_type(self, connection):
        return 'integer UNSIGNED'

पायथन वस्तुओं में मूल्यों को परिवर्तित करना

यदि आपका कस्टम django.db.models.Field वर्ग उन डेटा संरचनाओं से संबंधित है जो तार, दिनांक, पूर्णांक या फ़्लोट्स से अधिक जटिल हैं, तो from_db_value() और to_python() से ओवरराइड करने की आवश्यकता हो सकती है।

यदि फ़ील्ड उपवर्ग के लिए मौजूद है, तो from_db_value() सभी परिस्थितियों में बुलाया जाएगा जब डेटा डेटाबेस से लोड किया जाता है, जिसमें एग्रीगेट और values() कॉल शामिल हैं।

to_python() deserialization द्वारा कहा जाता है और रूपों से clean() विधि के दौरान उपयोग किया जाता है।

एक सामान्य नियम के रूप में, to_python() को निम्नलिखित में से किसी भी तर्क से इनायत करना चाहिए:

  • सही प्रकार का एक उदाहरण (उदाहरण के लिए, हमारे चल रहे उदाहरण में Hand )।
  • एक स्ट्रिंग
  • None (यदि क्षेत्र null=True अनुमति देता है)

हमारे HandField वर्ग में, हम डेटा को डेटाबेस में VARCHAR फ़ील्ड के रूप में संग्रहीत कर रहे हैं, इसलिए हमें स्ट्रिंग्स को संसाधित करने में सक्षम होना चाहिए और from_db_value() में Noneto_python() , हमें Hand इंस्टैंस को भी हैंडल करना होगा:

import re

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

def parse_hand(hand_string):
    """Takes a string of cards and splits into a full hand."""
    p1 = re.compile('.{26}')
    p2 = re.compile('..')
    args = [p2.findall(x) for x in p1.findall(hand_string)]
    if len(args) != 4:
        raise ValidationError(_("Invalid input for a Hand instance"))
    return Hand(*args)

class HandField(models.Field):
    # ...

    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return parse_hand(value)

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        if value is None:
            return value

        return parse_hand(value)

ध्यान दें कि हम हमेशा इन तरीकों से एक Hand उदाहरण लौटाते हैं। वह पायथन ऑब्जेक्ट प्रकार है जिसे हम मॉडल की विशेषता में संग्रहीत करना चाहते हैं।

to_python() , यदि मूल्य रूपांतरण के दौरान कुछ भी गलत होता है, तो आपको ValidationError अपवाद को उठाना चाहिए।

पायथन ऑब्जेक्ट को क्वेरी मान में परिवर्तित करना

चूंकि डेटाबेस का उपयोग करने के लिए दोनों तरीकों से रूपांतरण की आवश्यकता होती है, यदि आप to_python() को ओवरराइड करते हैं, तो आपको पायथन ऑब्जेक्ट्स को क्वेरी मान में बदलने के लिए get_prep_value() को ओवरराइड करना होगा।

उदाहरण के लिए:

class HandField(models.Field):
    # ...

    def get_prep_value(self, value):
        return ''.join([''.join(l) for l in (value.north,
                value.east, value.south, value.west)])

चेतावनी

यदि आपका कस्टम फ़ील्ड MySQL के लिए CHAR , VARCHAR या TEXT प्रकारों का उपयोग करता है, तो आपको यह सुनिश्चित करना चाहिए कि get_prep_value() हमेशा एक स्ट्रिंग प्रकार देता है। इन प्रकारों पर एक क्वेरी करने पर MySQL लचीला और अप्रत्याशित मिलान करता है और प्रदान किया गया मान एक पूर्णांक होता है, जो उनके परिणामों में अप्रत्याशित वस्तुओं को शामिल करने के लिए प्रश्नों का कारण बन सकता है। यदि आप हमेशा एक स्ट्रिंग प्रकार get_prep_value() से वापस करते हैं, तो यह समस्या नहीं हो सकती है।

डेटाबेस मूल्यों के लिए क्वेरी मान परिवर्तित करना

कुछ डेटा प्रकार (उदाहरण के लिए, दिनांक) को डेटाबेस बैकएंड द्वारा उपयोग किए जाने से पहले एक विशिष्ट प्रारूप में होना चाहिए। get_db_prep_value() वह विधि है जहां उन रूपांतरण किए जाने चाहिए। क्वेरी के लिए उपयोग किए जाने वाले विशिष्ट कनेक्शन को connection पैरामीटर के रूप में पास किया जाता है। यदि आवश्यक हो तो यह आपको बैकएंड-विशिष्ट रूपांतरण तर्क का उपयोग करने की अनुमति देता है।

उदाहरण के लिए, Django अपने BinaryField लिए निम्न विधि का उपयोग करता है:

def get_db_prep_value(self, value, connection, prepared=False):
    value = super().get_db_prep_value(value, connection, prepared)
    if value is not None:
        return connection.Database.Binary(value)
    return value

यदि आपके कस्टम फ़ील्ड को एक विशेष रूपांतरण की आवश्यकता होती है, तो सहेजा जा रहा है जो सामान्य क्वेरी मापदंडों के लिए उपयोग किए जाने वाले रूपांतरण के समान नहीं है, तो आप get_db_prep_save() को ओवरराइड कर सकते हैं।

बचत करने से पहले मानों में वृद्धि करना

यदि आप सहेजने से पहले मूल्य को प्रीप्रोसेस करना चाहते हैं, तो आप pre_save() उपयोग कर सकते हैं। उदाहरण के लिए, Django का DateTimeField इस विधि का उपयोग auto_now या auto_now_add के मामले में विशेषता को सही ढंग से सेट करने के लिए करता है।

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

मॉडल फ़ील्ड के लिए प्रपत्र फ़ील्ड निर्दिष्ट करना

ModelForm द्वारा उपयोग किए जाने वाले फ़ॉर्म फ़ील्ड को कस्टमाइज़ करने के लिए, आप formfield() को ओवरराइड कर सकते हैं।

प्रपत्र फ़ील्ड क्लास को form_class और choices_form_class तर्कों के माध्यम से निर्दिष्ट किया जा सकता है; उत्तरार्द्ध का उपयोग किया जाता है यदि फ़ील्ड में विकल्प निर्दिष्ट हैं, तो पूर्व। यदि ये तर्क प्रदान नहीं किए जाते हैं, तो CharField या TypedChoiceField का उपयोग किया जाएगा।

सभी kwargs डिक्शनरी सीधे फॉर्म फील्ड की __init__() पद्धति से पास की जाती है। आम तौर पर, आपको केवल form_class (और शायद choices_form_class ) तर्क के लिए एक अच्छा डिफ़ॉल्ट सेट अप करना होगा और फिर आगे की हैंडलिंग को पैरेंट क्लास को choices_form_class । इसके लिए आपको एक कस्टम फ़ॉर्म फ़ील्ड (और यहां तक ​​कि एक विजेट) भी लिखना पड़ सकता है। इस बारे में जानकारी के लिए प्रपत्र दस्तावेज़ीकरण देखें।

हमारे चल रहे उदाहरण को जारी रखते हुए, हम formfield() विधि को इस प्रकार लिख सकते हैं:

class HandField(models.Field):
    # ...

    def formfield(self, **kwargs):
        # This is a fairly standard way to set up some defaults
        # while letting the caller override them.
        defaults = {'form_class': MyFormField}
        defaults.update(kwargs)
        return super().formfield(**defaults)

यह मानता है कि हमने एक MyFormField फ़ील्ड क्लास (जिसका अपना डिफ़ॉल्ट विजेट है) आयात किया है। यह दस्तावेज़ कस्टम फ़ॉर्म फ़ील्ड लिखने के विवरण को कवर नहीं करता है।

अंतर्निहित फ़ील्ड प्रकारों का अनुकरण करना

यदि आपने एक db_type() विधि बनाई है, तो आपको get_internal_type() बारे में चिंता करने की आवश्यकता नहीं है - इसका बहुत उपयोग नहीं किया जाएगा। कभी-कभी, हालांकि, आपका डेटाबेस संग्रहण किसी अन्य फ़ील्ड के प्रकार के समान होता है, इसलिए आप सही कॉलम बनाने के लिए उस फ़ील्ड के अन्य तर्क का उपयोग कर सकते हैं।

उदाहरण के लिए:

class HandField(models.Field):
    # ...

    def get_internal_type(self):
        return 'CharField'

कोई फर्क नहीं पड़ता कि कौन सा डेटाबेस हम उपयोग कर रहे हैं, इसका मतलब यह है कि migrate और अन्य SQL कमांड एक स्ट्रिंग को संग्रहीत करने के लिए सही कॉलम प्रकार बनाते हैं।

यदि get_internal_type() एक स्ट्रिंग देता है जो Django को डेटाबेस बैकएंड के लिए ज्ञात नहीं है जिसका आप उपयोग कर रहे हैं - अर्थात, यह django.db.backends.<db_name>.base.DatabaseWrapper.data_types में दिखाई नहीं देता है django.db.backends.<db_name>.base.DatabaseWrapper.data_types - स्ट्रिंग अभी भी रहेगी। धारावाहिक द्वारा उपयोग किया जाता है, लेकिन डिफ़ॉल्ट db_type() विधि None लौटाएगा। यह उपयोगी हो सकता है क्यों कारणों के लिए db_type() का प्रलेखन देखें। धारावाहिक के लिए क्षेत्र के प्रकार के रूप में एक वर्णनात्मक स्ट्रिंग लाना एक उपयोगी विचार है यदि आप कभी भी Django के बाहर किसी अन्य स्थान पर क्रमिक आउटपुट का उपयोग करने जा रहे हैं।

क्रमांकन के लिए फ़ील्ड डेटा परिवर्तित करना

अनुकूलित करने के लिए कि कैसे मूल्यों को क्रमिक रूप से क्रमबद्ध किया जाता है, आप value_to_string() को ओवरराइड कर सकते हैं। क्रमांकन से पहले फ़ील्ड का मान प्राप्त करने का सबसे अच्छा तरीका है value_from_object() का उपयोग करना। उदाहरण के लिए, चूंकि HandField अपने डेटा संग्रहण के लिए स्ट्रिंग्स का उपयोग करता है, इसलिए हम कुछ मौजूदा रूपांतरण कोड का पुन: उपयोग कर सकते हैं:

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self.value_from_object(obj)
        return self.get_prep_value(value)

कुछ सामान्य सलाह

एक कस्टम फ़ील्ड लिखना एक मुश्किल प्रक्रिया हो सकती है, खासकर यदि आप अपने पायथन प्रकारों और अपने डेटाबेस और क्रमांकन प्रारूपों के बीच जटिल रूपांतरण कर रहे हैं। यहाँ चीजों को और अधिक सुचारू बनाने के लिए कुछ सुझाव दिए गए हैं:

  1. प्रेरणा के लिए मौजूदा Django फ़ील्ड्स ( django/db/models/fields/__init__.py )। एक ऐसा क्षेत्र खोजने की कोशिश करें जो आप चाहते हैं और खरोंच से पूरी तरह से नया क्षेत्र बनाने के बजाय इसे थोड़ा बढ़ाएं।
  2. उस __str__() एक __str__() विधि रखें, __str__() आप फ़ील्ड के रूप में लपेट रहे हैं। बहुत सारे स्थान हैं जहां फ़ील्ड कोड का डिफ़ॉल्ट व्यवहार मूल्य पर str() को कॉल करना है। (इस दस्तावेज़ में हमारे उदाहरणों में, value एक Hand उदाहरण होगा, HandField नहीं)। इसलिए यदि आपकी __str__() विधि स्वचालित रूप से आपके पायथन ऑब्जेक्ट के स्ट्रिंग रूप में परिवर्तित हो जाती है, तो आप अपने आप को बहुत काम बचा सकते हैं।

एक FileField उपवर्ग लेखन

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

Django एक File वर्ग प्रदान करता है, जिसका उपयोग फ़ाइल की सामग्री और संचालन के लिए एक प्रॉक्सी के रूप में किया जाता है। यह कस्टमाइज़ किया जा सकता है कि फ़ाइल को कैसे एक्सेस किया जाए, और क्या तरीके उपलब्ध हैं। यह django.db.models.fields.files पर django.db.models.fields.files , और इसका डिफ़ॉल्ट व्यवहार फ़ाइल प्रलेखन में समझाया गया है।

एक बार File का एक उपवर्ग बनाने के बाद, नया FileField उपवर्ग का उपयोग करने के लिए कहा जाना चाहिए। ऐसा करने के लिए, बस File उपवर्ग के विशेष attr_class विशेषता के लिए नया File उपवर्ग असाइन करें।

कुछ सुझाव

उपरोक्त विवरण के अलावा, कुछ दिशानिर्देश हैं जो क्षेत्र के कोड की दक्षता और पठनीयता में सुधार कर सकते हैं।

  1. Django के खुद के ImageField ( django/db/models/fields/files.py ) का स्रोत इस बात का एक शानदार उदाहरण है कि किसी विशेष प्रकार की फ़ाइल का समर्थन करने के लिए FileField को कैसे FileField जाए, क्योंकि यह ऊपर वर्णित सभी तकनीकों को शामिल करता है।
  2. जहाँ भी संभव हो कैश फ़ाइल विशेषताएँ। चूंकि फ़ाइलों को दूरस्थ भंडारण प्रणालियों में संग्रहीत किया जा सकता है, इसलिए उन्हें पुनर्प्राप्त करना अतिरिक्त समय, या यहां तक ​​कि धन भी हो सकता है, जो हमेशा आवश्यक नहीं होता है। एक बार जब किसी फ़ाइल को उसकी सामग्री के बारे में कुछ डेटा प्राप्त करने के लिए पुनर्प्राप्त किया जाता है, तो उस जानकारी के लिए बाद के कॉल पर फ़ाइल को पुनर्प्राप्त करने के लिए उस डेटा को जितना संभव हो उतना कम करना चाहिए।

Original text