व्यावसायिक तर्क और django में डेटा एक्सेस का पृथक्करण


484

मैं Django में एक परियोजना लिख ​​रहा हूं और मैं देखता हूं कि 80% कोड फ़ाइल में है models.py। यह कोड भ्रामक है और एक निश्चित समय के बाद, मैं यह समझना बंद कर देता हूं कि वास्तव में क्या हो रहा है।

यहाँ मुझे परेशान है:

  1. मुझे यह बदसूरत लगता है कि मेरा मॉडल स्तर (जो केवल डेटाबेस से डेटा के साथ काम के लिए जिम्मेदार माना जाता था) भी ईमेल भेज रहा है, अन्य सेवाओं के लिए एपीआई पर चलना आदि।
  2. इसके अलावा, मुझे व्यावसायिक तर्क को देखने के लिए अस्वीकार्य लगता है, क्योंकि इस तरह इसे नियंत्रित करना मुश्किल हो जाता है। उदाहरण के लिए, मेरे आवेदन में नए उदाहरण बनाने के लिए कम से कम तीन तरीके हैं User, लेकिन तकनीकी रूप से इसे समान रूप से बनाना चाहिए।
  3. मैं हमेशा ध्यान नहीं देता कि मेरे मॉडल के तरीके और गुण कब गैर-नियतात्मक हो जाते हैं और जब वे दुष्प्रभाव पैदा करते हैं।

ये रहा एक सरल उदाहरण। सबसे पहले, Userमॉडल इस तरह था:

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

समय के साथ, यह इस में बदल गया:

class User(db.Models):

    def get_present_name(self): 
        # property became non-deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

    def activate(self):
        # method now has a side effect (send message to user)
        self.status = 'activated'
        self.save()
        send_mail('Your account is activated!', '…', [self.email])

मैं अपने कोड में संस्थाओं को अलग करना चाहता हूं:

  1. मेरे डेटाबेस, डेटाबेस स्तर की प्रविष्टियाँ: मेरे आवेदन में क्या है?
  2. मेरे आवेदन की इकाइयाँ, व्यावसायिक तर्क स्तर: मेरा आवेदन क्या कर सकता है?

ऐसे दृष्टिकोण को लागू करने के लिए कौन सी अच्छी प्रथाएं हैं जो कि Django में लागू की जा सकती हैं?


14
संकेतों के बारे में पढ़ें
कॉन्सटैंट

1
अच्छी तरह से आपने टैग को हटा दिया है, लेकिन आप DCI का उपयोग सिस्टम की कार्यक्षमता (कार्यक्षमता / डेटा) और सिस्टम (डेटा / डोमेन मॉडल) के विभाजन को पूरा करने के लिए कर सकते हैं
Rune FS

2
आप सिग्नल कॉलबैक में सभी व्यापारिक तर्क को लागू करने का प्रस्ताव देते हैं? दुर्भाग्य से, मेरे सभी एप्लिकेशन डेटाबेस में घटनाओं से जुड़े नहीं हो सकते हैं।
defuz

रुण एफएस, मैंने डीसीआई का उपयोग करने की कोशिश की, लेकिन मुझे ऐसा लग रहा था कि इसे मेरे प्रोजेक्ट के लिए बहुत ज्यादा ज़रूरत नहीं है: संदर्भ, वस्तुओं के लिए मिश्रक के रूप में भूमिकाओं की परिभाषा, आदि "जुदाई" और "जुदाई" का एक आसान तरीका है। है"? क्या आप एक न्यूनतम उदाहरण दे सकते हैं?
defuz

जवाबों:


634

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

इसके अलावा, मैंने आपके प्रश्न के तीसरे भाग की व्याख्या की है: इन मॉडलों को अलग रखने में विफलता को कैसे नोटिस किया जाए।

ये दो बहुत अलग अवधारणाएँ हैं और इन्हें अलग रखना हमेशा कठिन होता है। हालांकि, कुछ सामान्य पैटर्न और उपकरण हैं जिनका उपयोग इस उद्देश्य के लिए किया जा सकता है।

डोमेन मॉडल के बारे में

पहली चीज जिसे आपको पहचानना है, वह यह है कि आपका डोमेन मॉडल वास्तव में डेटा के बारे में नहीं है; यह क्रियाओं और प्रश्नों के बारे में है जैसे "इस उपयोगकर्ता को सक्रिय करें", "इस उपयोगकर्ता को निष्क्रिय करें", "कौन से उपयोगकर्ता वर्तमान में सक्रिय हैं?", और "यह उपयोगकर्ता का नाम क्या है?"। शास्त्रीय शब्दों में: यह प्रश्नों और आदेशों के बारे में है ।

कमांडों में सोच रहा था

आइए अपने उदाहरण में आज्ञाओं को देखकर शुरू करें: "इस उपयोगकर्ता को सक्रिय करें" और "इस उपयोगकर्ता को निष्क्रिय करें"। आदेशों के बारे में अच्छी बात यह है कि वे आसानी से छोटे दिए गए जब-तब परिदृश्य द्वारा व्यक्त किए जा सकते हैं:

दिए गए किसी निष्क्रिय उपयोगकर्ता
जब व्यवस्थापक को सक्रिय करता है इस प्रयोक्ता
तो उपयोगकर्ता सक्रिय हो जाता है
और एक पुष्टिकरण ई-मेल उपयोगकर्ता के लिए भेज दिया जाता है
और एक प्रवेश सिस्टम लॉग में जोड़ा जाता है
(आदि आदि)

इस तरह के परिदृश्य यह देखने के लिए उपयोगी हैं कि आपके बुनियादी ढांचे के विभिन्न भाग एक ही आदेश से कैसे प्रभावित हो सकते हैं - इस मामले में आपका डेटाबेस (किसी प्रकार का 'सक्रिय' ध्वज), आपका मेल सर्वर, आपका सिस्टम लॉग इत्यादि।

इस तरह का परिदृश्य वास्तव में टेस्ट ड्रिवेन डेवलपमेंट के माहौल को स्थापित करने में आपकी मदद करता है।

और अंत में, कमांड्स में सोचना वास्तव में आपको एक कार्य-उन्मुख अनुप्रयोग बनाने में मदद करता है। आपके उपयोगकर्ता इस की सराहना करेंगे :-)

व्यक्त करना

Django व्यक्त करने के दो आसान तरीके प्रदान करता है; वे दोनों वैध विकल्प हैं और दो दृष्टिकोणों को मिलाना असामान्य नहीं है।

सेवा परत

सेवा मॉड्यूल पहले से ही किया गया है @Hedde द्वारा वर्णित । यहां आप एक अलग मॉड्यूल को परिभाषित करते हैं और प्रत्येक कमांड को एक फ़ंक्शन के रूप में दर्शाया जाता है।

services.py

def activate_user(user_id):
    user = User.objects.get(pk=user_id)

    # set active flag
    user.active = True
    user.save()

    # mail user
    send_mail(...)

    # etc etc

रूपों का उपयोग करना

दूसरा तरीका प्रत्येक कमांड के लिए एक Django फॉर्म का उपयोग करना है। मैं इस दृष्टिकोण को पसंद करता हूं, क्योंकि यह कई निकट से संबंधित पहलुओं को जोड़ता है:

  • कमांड का निष्पादन (यह क्या करता है?)
  • कमांड मापदंडों का सत्यापन (क्या यह ऐसा कर सकता है?)
  • कमांड की प्रस्तुति (मैं यह कैसे कर सकता हूं?)

forms.py

class ActivateUserForm(forms.Form):

    user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
    # the username select widget is not a standard Django widget, I just made it up

    def clean_user_id(self):
        user_id = self.cleaned_data['user_id']
        if User.objects.get(pk=user_id).active:
            raise ValidationError("This user cannot be activated")
        # you can also check authorizations etc. 
        return user_id

    def execute(self):
        """
        This is not a standard method in the forms API; it is intended to replace the 
        'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern. 
        """
        user_id = self.cleaned_data['user_id']

        user = User.objects.get(pk=user_id)

        # set active flag
        user.active = True
        user.save()

        # mail user
        send_mail(...)

        # etc etc

विचार में प्रश्न

आपके उदाहरण में कोई प्रश्न नहीं था, इसलिए मैंने कुछ उपयोगी प्रश्नों को बनाने की स्वतंत्रता ली। मैं "प्रश्न" शब्द का उपयोग करना पसंद करता हूं, लेकिन प्रश्न शास्त्रीय शब्दावली है। दिलचस्प प्रश्न हैं: "इस उपयोगकर्ता का नाम क्या है?", "क्या यह उपयोगकर्ता लॉग इन कर सकता है?", "मुझे निष्क्रिय उपयोगकर्ताओं की सूची दिखाएं", और "निष्क्रिय उपयोगकर्ताओं का भौगोलिक वितरण क्या है?"

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

उपयोगकर्ता इंटरफ़ेस को बेहतर बनाने के लिए केवल प्रेजेंटेशनल क्वेश्चन किए जाते हैं। व्यावसायिक तर्क प्रश्नों के उत्तर सीधे आपके आदेशों के निष्पादन को प्रभावित करते हैं। रिपोर्टिंग के प्रश्न केवल विश्लेषणात्मक उद्देश्यों के लिए हैं और इसमें समय की कमी है। ये श्रेणियां परस्पर अनन्य नहीं हैं।

दूसरा सवाल यह है: "क्या मुझे उत्तरों पर पूरा नियंत्रण है?" उदाहरण के लिए, जब उपयोगकर्ता के नाम (इस संदर्भ में) का प्रश्न किया जाता है तो परिणाम पर हमारा कोई नियंत्रण नहीं होता है, क्योंकि हम बाहरी एपीआई पर निर्भर होते हैं।

प्रश्न बनाना

Django में सबसे बुनियादी क्वेरी प्रबंधक ऑब्जेक्ट का उपयोग है:

User.objects.filter(active=True)

बेशक, यह केवल तभी काम करता है जब डेटा वास्तव में आपके डेटा मॉडल में दर्शाया गया हो। ऐसी स्थिति हर बार नहीं होती है। उन मामलों में, आप नीचे दिए गए विकल्पों पर विचार कर सकते हैं।

कस्टम टैग और फ़िल्टर

पहला विकल्प उन प्रश्नों के लिए उपयोगी है जो केवल प्रस्तुतिकरण हैं: कस्टम टैग और टेम्पलेट फ़िल्टर।

template.html

<h1>Welcome, {{ user|friendly_name }}</h1>

template_tags.py

@register.filter
def friendly_name(user):
    return remote_api.get_cached_name(user.id)

क्वेरी के तरीके

अगर आपकी क्वेरी केवल प्रस्तुतिकरण नहीं है, तो आप अपने लिए क्वेरी जोड़ सकता है services.py (यदि आपको लगता है कि प्रयोग कर रहे हैं), या एक परिचय queries.py मॉड्यूल:

queries.py

def inactive_users():
    return User.objects.filter(active=False)


def users_called_publysher():
    for user in User.objects.all():
        if remote_api.get_cached_name(user.id) == "publysher":
            yield user 

प्रॉक्सी मॉडल

व्यावसायिक तर्क और रिपोर्टिंग के संदर्भ में प्रॉक्सी मॉडल बहुत उपयोगी हैं। आप मूल रूप से अपने मॉडल के एक बढ़ाए गए सबसेट को परिभाषित करते हैं। आप Manager.get_queryset()विधि को ओवरराइड करके किसी प्रबंधक के आधार QuerySet को ओवरराइड कर सकते हैं ।

models.py

class InactiveUserManager(models.Manager):
    def get_queryset(self):
        query_set = super(InactiveUserManager, self).get_queryset()
        return query_set.filter(active=False)

class InactiveUser(User):
    """
    >>> for user in InactiveUser.objects.all():
    …        assert user.active is False 
    """

    objects = InactiveUserManager()
    class Meta:
        proxy = True

क्वेरी मॉडल

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

models.py

class InactiveUserDistribution(models.Model):
    country = CharField(max_length=200)
    inactive_user_count = IntegerField(default=0)

पहला विकल्प इन मॉडलों को आपकी आज्ञाओं में अपडेट करना है। यह बहुत उपयोगी है अगर ये मॉडल केवल एक या दो कमांड द्वारा बदल दिए जाते हैं।

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

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

signals.py

user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        user_activated.send_robust(sender=self, user=user)

models.py

class InactiveUserDistribution(models.Model):
    # see above

@receiver(user_activated)
def on_user_activated(sender, **kwargs):
        user = kwargs['user']
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

इसे साफ रखना

इस दृष्टिकोण का उपयोग करते समय, यह निर्धारित करना हास्यास्पद हो जाता है कि आपका कोड साफ रहता है या नहीं। बस इन दिशानिर्देशों का पालन करें:

  • क्या मेरे मॉडल में ऐसी विधियाँ हैं जो डेटाबेस स्थिति को प्रबंधित करने से अधिक हैं? आपको एक आदेश निकालना चाहिए।
  • क्या मेरे मॉडल में ऐसे गुण हैं जो डेटाबेस फ़ील्ड पर मैप नहीं करते हैं? आपको एक क्वेरी निकालना चाहिए।
  • क्या मेरा मॉडल संदर्भ संरचना है जो मेरा डेटाबेस (जैसे मेल) नहीं है? आपको एक आदेश निकालना चाहिए।

वही विचारों के लिए जाता है (क्योंकि विचार अक्सर एक ही समस्या से ग्रस्त होते हैं)।

  • क्या मेरा विचार सक्रिय रूप से डेटाबेस मॉडल का प्रबंधन करता है? आपको एक आदेश निकालना चाहिए।

कुछ सन्दर्भ

Django प्रलेखन: प्रॉक्सी मॉडल

Django प्रलेखन: संकेत

वास्तुकला: डोमेन संचालित डिजाइन


11
DDD को django-related प्रश्न में शामिल करते हुए उत्तर को देखना अच्छा है। सिर्फ इसलिए कि Django दृढ़ता के लिए ActiveRecord को नियुक्त करता है इसका मतलब यह नहीं है कि चिंताओं को अलग करना खिड़की से बाहर जाना चाहिए। बहुत बढ़िया जवाब।
स्कॉट कोट

6
अगर मैं यह पुष्टि करना चाहता हूं कि उस वस्तु को हटाने से पहले, जो उपयोगकर्ता के लिए लोग्ड यूजर है, तो क्या मुझे यह देखना चाहिए कि वह व्यू में है या फॉर्म / सर्विस मॉड्यूल में है?
इवान

6
@ इवान: दोनों। यह फॉर्म / सर्विस मॉड्यूल में होना चाहिए क्योंकि यह आपके व्यापार की कमी का हिस्सा है। यह दृश्य में भी होना चाहिए क्योंकि आपको केवल ऐसी क्रियाएं प्रस्तुत करनी चाहिए जिन्हें उपयोगकर्ता वास्तव में निष्पादित कर सकते हैं।
युवाशाल

4
कस्टम प्रबंधक विधियाँ प्रश्नों को कार्यान्वित करने का एक अच्छा तरीका है User.objects.inactive_users():। लेकिन यहाँ प्रॉक्सी मॉडल उदाहरण IMO गलत शब्दार्थ की ओर जाता है: u = InactiveUser.objects.all()[0]; u.active = True; u.save()और अभी तक isinstance(u, InactiveUser) == True। इसके अलावा, मैं कई मामलों में एक क्वेरी मॉडल को बनाए रखने के लिए एक प्रभावी तरीके का उल्लेख करना चाहता हूं जो एक डीबी दृश्य के साथ है।
आर्येह लीब तौआरोग

1
@adnanmuttaleb यह सही है। ध्यान दें कि उत्तर केवल "डोमेन मॉडल" शब्द का उपयोग करता है। मैंने DDD के लिंक को शामिल नहीं किया है क्योंकि मेरा उत्तर DDD है, लेकिन क्योंकि यह पुस्तक आपको डोमेन मॉडल के बारे में सोचने में मदद करने में बहुत अच्छा काम करती है।
पबलीशर

148

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

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

services.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    return Book.objects.filter(**filters)[:limit]  # list[:None] will return the entire list

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

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


8
मुझे सामान्य दृष्टिकोण पसंद है, हालांकि मेरी समझ से आपके विशिष्ट उदाहरण को आमतौर पर एक प्रबंधक के रूप में लागू किया जाएगा ।
अरी

9
@ जरुरी नहीं, शायद एक बेहतर उदाहरण, एक webshop सेवाओं के लिए कार्ट सेशन जनरेट करने जैसी चीजें शामिल होंगी, अतुल्यकालिक कार्य जैसे उत्पाद रेटिंग गणना, ईमेल वगैरह बनाना और भेजना
Hedde van der Heide

4
मुझे यह दृष्टिकोण बहुत पसंद है, आने वाले फ्रॉ जावा भी। मैं अजगर के लिए नया हूँ, आप किस तरह से व्यू-थ्रू टेस्ट करेंगे? आप सेवा परत का उपयोग कैसे करेंगे (यदि, उदाहरण के लिए, सेवा कुछ दूरस्थ एपीआई कॉल करती है)?
तैमूरज

71

सबसे पहले, अपने आप को दोहराएं नहीं

फिर, कृपया सावधान रहें कि अधिकता के लिए नहीं, कभी-कभी यह सिर्फ समय की बर्बादी है, और किसी को इस बात पर ध्यान केंद्रित करने से रोकता है कि क्या महत्वपूर्ण है। समय-समय पर अजगर के ज़ेन की समीक्षा करें ।

सक्रिय परियोजनाओं पर एक नज़र डालें

  • अधिक लोगों = और अधिक को ठीक से व्यवस्थित करने की आवश्यकता है
  • Django भंडार वे एक सीधी संरचना है।
  • पिप भंडार वे एक straigtforward निर्देशिका संरचना है।
  • कपड़े भंडार भी एक अच्छा एक को देखने के लिए है।

    • आप अपने सभी मॉडलों को नीचे रख सकते हैं yourapp/models/logicalgroup.py
  • जैसे User, Groupऔर संबंधित मॉडल के तहत जा सकते हैंyourapp/models/users.py
  • जैसे Poll, Question, Answer... नीचे जा सकता हैyourapp/models/polls.py
  • लोड क्या आप के __all__अंदर की जरूरत हैyourapp/models/__init__.py

एमवीसी के बारे में अधिक

  • मॉडल आपका डेटा है
    • इसमें आपका वास्तविक डेटा शामिल है
    • इसमें आपका सत्र / कुकी / कैश / एफएस / इंडेक्स डेटा भी शामिल है
  • उपयोगकर्ता मॉडल में हेरफेर करने के लिए नियंत्रक के साथ बातचीत करता है
    • यह एक एपीआई या एक दृश्य हो सकता है जो आपके डेटा को बचाता / अपडेट करता है
    • यह request.GET/ request.POST... आदि के साथ ट्यून किया जा सकता है
    • पेजिंग या फ़िल्टरिंग के बारे में भी सोचें ।
  • डेटा दृश्य को अद्यतन करता है
    • टेम्प्लेट डेटा लेते हैं और उसी के अनुसार प्रारूपित करते हैं
    • API भी w / o टेम्प्लेट दृश्य का हिस्सा हैं; जैसे tastypieयाpiston
    • यह मिडिलवेयर के लिए भी होना चाहिए।

का लाभ उठाएं मिडलवेयर / templatetags

  • यदि आपको प्रत्येक अनुरोध के लिए कुछ काम करने की आवश्यकता है, तो मिडलवेयर जाने का एक तरीका है।
    • जैसे टाइमस्टैम्प जोड़ना
    • जैसे पेज हिट के बारे में मीट्रिक अपडेट करना
    • उदाहरण के लिए एक कैश आबाद
  • यदि आपके पास कोड के स्निपेट्स हैं जो हमेशा फॉरमेटिंग ऑब्जेक्ट्स के लिए reoccur होते हैं, तो templatetags अच्छे हैं।
    • उदाहरण के लिए सक्रिय टैब / यूआरएल ब्रेडक्रंब

मॉडल प्रबंधकों का लाभ उठाएं

  • बनाने Userमें एक जा सकते हैं UserManager(models.Manager)
  • उदाहरणों के लिए gory विवरण पर जाना चाहिए models.Model
  • के लिए रक्तमय विवरण querysetएक में जा सकते हैं models.Manager
  • आप Userएक समय में एक बनाना चाहते हैं , तो आप सोच सकते हैं कि यह मॉडल पर ही रहना चाहिए, लेकिन ऑब्जेक्ट बनाते समय, आपके पास संभवतः सभी विवरण नहीं हैं:

उदाहरण:

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

जहाँ संभव हो वहाँ रूपों का उपयोग करें

यदि आपके पास एक मॉडल के लिए नक्शे हैं, तो बहुत सारे बॉयलरप्लेट कोड को समाप्त किया जा सकता है। ModelForm documentationबहुत अच्छा है। यदि आपके पास बहुत अधिक अनुकूलन है (या कभी-कभी अधिक उन्नत उपयोगों के लिए चक्रीय आयात त्रुटियों से बचने के लिए) मॉडल कोड से फॉर्म के लिए अलग कोड अच्छा हो सकता है।

जब संभव हो प्रबंधन आदेशों का उपयोग करें

  • जैसे yourapp/management/commands/createsuperuser.py
  • जैसे yourapp/management/commands/activateinbulk.py

यदि आपके पास व्यावसायिक तर्क है, तो आप इसे अलग कर सकते हैं

  • django.contrib.auth बैकेंड का उपयोग करता है , जैसे db का बैकएंड है ... आदि।
  • settingअपने व्यावसायिक तर्क के लिए जोड़ें (उदाहरण के लिए AUTHENTICATION_BACKENDS)
  • आप उपयोग कर सकते हैं django.contrib.auth.backends.RemoteUserBackend
  • आप उपयोग कर सकते हैं yourapp.backends.remote_api.RemoteUserBackend
  • आप उपयोग कर सकते हैं yourapp.backends.memcached.RemoteUserBackend
  • बैकेंड को कठिन व्यावसायिक तर्क सौंपते हैं
  • इनपुट / आउटपुट पर अपेक्षा को सही सेट करना सुनिश्चित करें।
  • व्यवसाय तर्क बदलना एक सेटिंग को बदलना जितना आसान है :)

बैकेंड उदाहरण:

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

हो सकता हे:

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

अधिक डिजाइन पैटर्न के बारे में

इंटरफ़ेस सीमाओं के बारे में अधिक

  • क्या कोड आप वास्तव में मॉडल का उपयोग करना चाहते हैं? ->yourapp.models
  • क्या व्यापार तर्क का भाग है? ->yourapp.vendor
  • क्या जेनेरिक टूल्स / लिबास का कोड हिस्सा है? ->yourapp.libs
  • क्या व्यापार तर्क के लिबास का हिस्सा है? -> yourapp.libs.vendorयाyourapp.vendor.libs
  • यहाँ एक अच्छा है: क्या आप अपने कोड का स्वतंत्र रूप से परीक्षण कर सकते हैं?
    • हाँ अच्छा :)
    • नहीं, आपको इंटरफ़ेस समस्या हो सकती है
    • जब स्पष्ट अलगाव होता है, तो मॉकिंग के उपयोग के साथ एकतरफा हवा होनी चाहिए
  • क्या अलगाव तार्किक है?
    • हाँ अच्छा :)
    • नहीं, आपको अलग से उन तार्किक अवधारणाओं का परीक्षण करने में परेशानी हो सकती है।
  • क्या आपको लगता है कि 10x अधिक कोड प्राप्त करने पर आपको रिफ्लेक्टर की आवश्यकता होगी?
    • हाँ, कोई अच्छा, कोई बानो नहीं, रिफ्लेक्टर बहुत काम का हो सकता है
    • नहीं, यह सिर्फ कमाल है!

संक्षेप में, आप कर सकते थे

  • yourapp/core/backends.py
  • yourapp/core/models/__init__.py
  • yourapp/core/models/users.py
  • yourapp/core/models/questions.py
  • yourapp/core/backends.py
  • yourapp/core/forms.py
  • yourapp/core/handlers.py
  • yourapp/core/management/commands/__init__.py
  • yourapp/core/management/commands/closepolls.py
  • yourapp/core/management/commands/removeduplicates.py
  • yourapp/core/middleware.py
  • yourapp/core/signals.py
  • yourapp/core/templatetags/__init__.py
  • yourapp/core/templatetags/polls_extras.py
  • yourapp/core/views/__init__.py
  • yourapp/core/views/users.py
  • yourapp/core/views/questions.py
  • yourapp/core/signals.py
  • yourapp/lib/utils.py
  • yourapp/lib/textanalysis.py
  • yourapp/lib/ratings.py
  • yourapp/vendor/backends.py
  • yourapp/vendor/morebusinesslogic.py
  • yourapp/vendor/handlers.py
  • yourapp/vendor/middleware.py
  • yourapp/vendor/signals.py
  • yourapp/tests/test_polls.py
  • yourapp/tests/test_questions.py
  • yourapp/tests/test_duplicates.py
  • yourapp/tests/test_ratings.py

या कुछ और जो आपकी मदद करता है; आपके लिए आवश्यक इंटरफेस और सीमाओं को खोजने से आपको मदद मिलेगी।


27

Django एमवीसी की थोड़ा संशोधित प्रकार को रोजगार देता है। Django में "नियंत्रक" की कोई अवधारणा नहीं है। निकटतम प्रॉक्सी एक "दृश्य" है, जो MVC के साथ भ्रम का कारण बनता है, क्योंकि MVC में एक दृश्य Django के "टेम्पलेट" की तरह अधिक होता है।

Django में, एक "मॉडल" केवल एक डेटाबेस अमूर्त नहीं है। कुछ मामलों में, यह MVC के नियंत्रक के रूप में Django के "दृश्य" के साथ कर्तव्य साझा करता है। यह एक उदाहरण के साथ जुड़े व्यवहार की संपूर्णता रखता है। यदि उस उदाहरण को व्यवहार के भाग के रूप में एक बाहरी एपीआई के साथ बातचीत करने की आवश्यकता है, तो वह अभी भी मॉडल कोड है। वास्तव में, मॉडल को डेटाबेस के साथ बातचीत करने की आवश्यकता नहीं होती है, इसलिए आप अनुमान लगा सकते हैं कि मॉडल पूरी तरह से बाहरी एपीआई के लिए एक इंटरैक्टिव परत के रूप में मौजूद हैं। यह एक "मॉडल" की एक बहुत अधिक मुक्त अवधारणा है।


7

Django में, MVC संरचना के रूप में क्रिस प्रैट ने कहा, अन्य चौखटे में इस्तेमाल किए जाने वाले शास्त्रीय MVC मॉडल से अलग, मुझे लगता है कि ऐसा करने का मुख्य कारण बहुत सख्त आवेदन संरचना से बचना है, जैसे अन्य MVC चौखटे में होता है जैसे CakePHP।

Django में, MVC को निम्नलिखित तरीके से लागू किया गया था:

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

मॉडल परत एन्कैप्सुलेशन, अमूर्तता, मान्यता, बुद्धिमत्ता देता है और आपके डेटा को ऑब्जेक्ट-ओरिएंटेड बनाता है (वे कहते हैं कि किसी दिन डीबीएमएस भी होगा)। इसका मतलब यह नहीं है कि आपको विशाल मॉडल बनाने चाहिए फाइलें (वास्तव में एक बहुत अच्छी सलाह है कि अपने मॉडल को अलग-अलग फाइलों में विभाजित करें, उन्हें 'मॉडल' नामक एक फ़ोल्डर में डालें, इस में एक '__init__.py' फाइल बनाएं फ़ोल्डर जहाँ आप अपने सभी मॉडलों को आयात करते हैं और अंत में मॉडल की विशेषता 'app_label' का उपयोग करते हैं। मॉडल वर्ग)। मॉडल को आपको डेटा के साथ काम करने से रोकना चाहिए, यह आपके एप्लिकेशन को सरल बना देगा। यदि आवश्यक हो, तो आपको अपने मॉडलों के लिए "उपकरण" जैसे बाहरी कक्षाएं भी बनानी चाहिए। आप अपने मॉडल के मेटा क्लास के 'एब्सट्रैक्ट' विशेषता को 'ट्रू' में सेट करके मॉडल में विरासत का उपयोग कर सकते हैं।

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

यदि फॉर्म आपके लिए एनॉग नहीं हैं, तो आपको जादू करने के लिए अपनी खुद की कक्षाएं बनानी चाहिए, इसका एक बहुत अच्छा उदाहरण है एडमिन एप्लिकेशन: आप मॉडलएमिन कोड पढ़ सकते हैं, यह वास्तव में एक नियंत्रक के रूप में काम करता है। एक मानक संरचना नहीं है, मैं आपको मौजूदा Django एप्लिकेशन की जांच करने का सुझाव देता हूं, यह प्रत्येक मामले पर निर्भर करता है। यह Django डेवलपर्स का इरादा है, आप xml पार्सर क्लास, एपीआई कनेक्टर क्लास जोड़ सकते हैं, प्रदर्शन कार्यों के लिए सेलेरी जोड़ सकते हैं, रिएक्टर-आधारित एप्लिकेशन के लिए मुड़ सकते हैं, केवल ओआरएम का उपयोग कर सकते हैं, वेब सेवा बना सकते हैं, व्यवस्थापक एप्लिकेशन को संशोधित कर सकते हैं और बहुत कुछ। .. यह अच्छी गुणवत्ता कोड बनाने के लिए आपकी जिम्मेदारी है, MVC दर्शन का सम्मान करें या नहीं, इसे मॉड्यूल आधारित बनाएं और अपनी खुद की अमूर्त परतें बनाएं। यह बहुत लचीला है।

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

पुनश्च।: सामान्य अनुप्रयोग (मानक django से) से 'उपयोगकर्ता' वर्ग का उपयोग करें, आप उदाहरण के लिए उपयोगकर्ता प्रोफाइल बना सकते हैं, या कम से कम इसके कोड को पढ़ सकते हैं, यह आपके मामले के लिए उपयोगी होगा।


1

एक पुराना सवाल है, लेकिन मैं वैसे भी अपने समाधान की पेशकश करना चाहूंगा। यह स्वीकृति है कि मॉडल भी कुछ अतिरिक्त कार्यक्षमता की आवश्यकता होती है, जबकि यह वस्तुओं के भीतर रखने के लिए अजीब है पर आधारित है models.py । व्यक्तिगत स्वाद के आधार पर भारी व्यावसायिक तर्क को अलग से लिखा जा सकता है, लेकिन मुझे कम से कम मॉडल से संबंधित सब कुछ खुद करना पसंद है। यह समाधान उन लोगों का भी समर्थन करता है जो मॉडल के भीतर रखे गए सभी तर्क को पसंद करते हैं।

जैसे, मैंने एक हैक तैयार किया जो मुझे मॉडल परिभाषाओं से तर्क को अलग करने की अनुमति देता है और अभी भी मेरी आईडीई से सभी संकेत प्राप्त करता है।

लाभ स्पष्ट होना चाहिए, लेकिन यह कुछ सूची है जो मैंने देखा है:

  • DB परिभाषाएँ बस इतनी ही रहती हैं - कोई तर्क "कचरा" संलग्न नहीं है
  • मॉडल से संबंधित तर्क सभी को बड़े करीने से एक जगह पर रखा गया है
  • सभी सेवाओं (फॉर्म, आरईएसटी, विचार) में तर्क तक एक ही पहुंच बिंदु है
  • सभी के सर्वश्रेष्ठ: मुझे यह महसूस नहीं हुआ कि किसी भी कोड को एक बार फिर से लिखना होगा, जब मुझे पता चलेगा कि मेरे मॉडल-थ्रेड बहुत अव्यवस्थित हो गए हैं और तर्क को अलग करना पड़ा है। पृथक्करण सुचारू और पुनरावृत्त है: मैं एक समय या पूरी कक्षा या संपूर्ण मॉडल थिंकपैड पर एक कार्य कर सकता था।

मैं इसे पायथन 3.4 और अधिक से अधिक और Django 1.8 और अधिक से अधिक उपयोग कर रहा हूं।

एप्लिकेशन / models.py

....
from app.logic.user import UserLogic

class User(models.Model, UserLogic):
    field1 = models.AnyField(....)
    ... field definitions ...

एप्लिकेशन / तर्क / user.py

if False:
    # This allows the IDE to know about the User model and its member fields
    from main.models import User

class UserLogic(object):
    def logic_function(self: 'User'):
        ... code with hinting working normally ...

केवल एक चीज जो मैं समझ नहीं पा रहा हूं वह यह है कि मेरी आईडीई (इस मामले में PyCharm) को कैसे पहचानें कि UserLogic वास्तव में उपयोगकर्ता मॉडल है। लेकिन चूंकि यह स्पष्ट रूप से एक हैक है, इसलिए मैं selfपैरामीटर के लिए हमेशा निर्दिष्ट प्रकार के छोटे उपद्रव को स्वीकार करने में बहुत खुश हूं ।


वास्तव में मैं इसे दृष्टिकोण का उपयोग करने के लिए एक आसान के रूप में देखता हूं। लेकिन मैं अंतिम मॉडल को किसी अन्य फ़ाइल में ले जाऊंगा, और माडलहोम में इनहेरिट नहीं करूंगा। यह service.py जैसा होगा थे userlogic + मॉडल संघर्ष
Maks

1

मुझे आपसे सहमत होना होगा। Django में बहुत सारी संभावनाएं हैं लेकिन शुरुआत करने के लिए सबसे अच्छी जगह Django के डिजाइन दर्शन की समीक्षा है ।

  1. मॉडल प्रॉपर्टी से एपीआई कॉल करना आदर्श नहीं होगा, ऐसा लगता है कि यह देखने में ऐसा कुछ करने के लिए अधिक समझ में आता है और संभवत: चीजों को सूखा रखने के लिए एक सेवा परत बनाता है। यदि API पर कॉल गैर-अवरुद्ध है और कॉल एक महंगा है, तो सेवा कार्यकर्ता (एक कार्यकर्ता जो एक कतार से खपत करता है) को अनुरोध भेजना समझ में आता है।

  2. Django के डिजाइन दर्शन मॉडल के अनुसार एक "वस्तु" के हर पहलू को समझाया जाता है। तो उस वस्तु से संबंधित सभी व्यावसायिक तर्क वहां रहने चाहिए:

सभी प्रासंगिक डोमेन तर्क शामिल करें

मार्टिन फॉवलर के एक्टिव रिकॉर्ड डिज़ाइन पैटर्न के बाद मॉडल को एक "ऑब्जेक्ट" के हर पहलू को इनकैप्सुलेट करना चाहिए।

  1. आपके द्वारा वर्णित साइड इफेक्ट्स स्पष्ट हैं, यहां तर्क को क्वेरसेट्स और प्रबंधकों में बेहतर रूप से तोड़ा जा सकता है। यहाँ एक उदाहरण है:

    models.py

    import datetime
    
    from djongo import models
    from django.db.models.query import QuerySet
    from django.contrib import admin
    from django.db import transaction
    
    
    class MyUser(models.Model):
    
        present_name = models.TextField(null=False, blank=True)
        status = models.TextField(null=False, blank=True)
        last_active = models.DateTimeField(auto_now=True, editable=False)
    
        # As mentioned you could put this in a template tag to pull it
        # from cache there. Depending on how it is used, it could be
        # retrieved from within the admin view or from a custom view
        # if that is the only place you will use it.
        #def get_present_name(self):
        #    # property became non-deterministic in terms of database
        #    # data is taken from another service by api
        #    return remote_api.request_user_name(self.uid) or 'Anonymous'
    
        # Moved to admin as an action
        # def activate(self):
        #     # method now has a side effect (send message to user)
        #     self.status = 'activated'
        #     self.save()
        #     # send email via email service
        #     #send_mail('Your account is activated!', '…', [self.email])
    
        class Meta:
            ordering = ['-id']  # Needed for DRF pagination
    
        def __unicode__(self):
            return '{}'.format(self.pk)
    
    
    class MyUserRegistrationQuerySet(QuerySet):
    
        def for_inactive_users(self):
            new_date = datetime.datetime.now() - datetime.timedelta(days=3*365)  # 3 Years ago
            return self.filter(last_active__lte=new_date.year)
    
        def by_user_id(self, user_ids):
            return self.filter(id__in=user_ids)
    
    
    class MyUserRegistrationManager(models.Manager):
    
        def get_query_set(self):
            return MyUserRegistrationQuerySet(self.model, using=self._db)
    
        def with_no_activity(self):
            return self.get_query_set().for_inactive_users()

    admin.py

    # Then in model admin
    
    class MyUserRegistrationAdmin(admin.ModelAdmin):
        actions = (
            'send_welcome_emails',
        )
    
        def send_activate_emails(self, request, queryset):
            rows_affected = 0
            for obj in queryset:
                with transaction.commit_on_success():
                    # send_email('welcome_email', request, obj) # send email via email service
                    obj.status = 'activated'
                    obj.save()
                    rows_affected += 1
    
            self.message_user(request, 'sent %d' % rows_affected)
    
    admin.site.register(MyUser, MyUserRegistrationAdmin)

0

मैं ज्यादातर चुने हुए उत्तर ( https://stackoverflow.com/a/12857584/871392 ) से सहमत हूं , लेकिन मेकिंग क्वैरिज सेक्शन में विकल्प जोड़ना चाहता हूं।

कोई फ़िल्टर क्वेरीज़ आदि के लिए मॉडल के लिए क्वेरसेट कक्षाएं परिभाषित कर सकता है। उसके बाद आप इस क्वेरी क्लास को मॉडल के मैनेजर के लिए प्रॉक्सी कर सकते हैं, जैसे बिल्ड-इन मैनेजर और क्वेरसेट क्लास करते हैं।

हालाँकि, यदि आपको एक डोमेन मॉडल प्राप्त करने के लिए कई डेटा मॉडल को क्वेरी करना पड़ता है, तो मुझे पहले से सुझाए गए अलग मॉड्यूल में इसे रखना अधिक उचित लगता है।


0

पेशेवरों और विपक्षों के साथ विभिन्न विकल्पों पर सबसे व्यापक लेख:

  1. आइडिया # 1: फैट मॉडल
  2. आइडिया # 2: व्यूज / फॉर्म में बिजनेस लॉजिक डालना
  3. आइडिया # 3: सेवाएं
  4. विचार # 4: क्वेरीसैट / प्रबंधक
  5. निष्कर्ष

स्रोत: https://sunscrapers.com/blog/where-to-put-business-logic-django/


आपको कुछ स्पष्टीकरण जोड़ना चाहिए।
m02ph3u5

-6

Django को आसानी से वेब पृष्ठों को वितरित करने के लिए उपयोग किया जाता है। यदि आप इससे सहमत नहीं हैं तो शायद आपको दूसरे उपाय का उपयोग करना चाहिए।

मैं मॉडल पर रूट या सामान्य संचालन (समान इंटरफ़ेस के लिए) और अन्य को मॉडल के नियंत्रक पर लिख रहा हूं। अगर मुझे अन्य मॉडल से एक ऑपरेशन की आवश्यकता है तो मैं इसके नियंत्रक को आयात करता हूं।

यह दृष्टिकोण मेरे लिए और मेरे अनुप्रयोगों की जटिलता के लिए पर्याप्त है।

हेदे की प्रतिक्रिया एक उदाहरण है जो स्वयं django और अजगर के लचीलेपन को दर्शाता है।

वैसे भी बहुत दिलचस्प सवाल!


9
कैसे कह रहा है कि यह आपके लिए काफी अच्छा है कि आप मेरी समझ के लिए मददगार हैं?
क्रिस वेसलिंग

1
Django में django.db.models के अलावा और भी बहुत कुछ है, लेकिन django मॉडल का उपयोग करते हुए अधिकांश पारिस्थितिकी तंत्र आपके मॉडल पर बहुत अधिक निर्भर करता है।
andho

1
सॉफ्टवेयर के विकास के लिए उपयोग किया जाने वाला डिजाइन पैटर्न। और django को आसानी से मध्य या बड़े पैमाने पर सॉफ्टवेयर वितरित करने के लिए आसानी से उपयोग किया जा सकता है न कि केवल वेब पेज!
मोहम्मद तोरकश्वंद
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.