Django'da iş mantığının ayrılması ve veri erişimi


484

Django'da bir proje yazıyorum ve kodun% 80'inin dosyada olduğunu görüyorum models.py. Bu kod kafa karıştırıcı ve belirli bir süre sonra gerçekten ne olduğunu anlamaya son veriyorum.

Beni rahatsız eden şey:

  1. Model seviyemin (sadece bir veritabanındaki verilerle çalışmaktan sorumlu olması gerekiyordu) de e-posta gönderiyor, API'yı diğer hizmetlere yürüyor vb.
  2. Ayrıca, iş mantığının görünüme yerleştirilmesini kabul edilemez buluyorum, çünkü bu şekilde kontrol edilmesi zorlaşıyor. Örneğin, uygulamamda yeni örnekleri oluşturmanın en az üç yolu vardır User, ancak teknik olarak bunları eşit olarak oluşturmalıdır.
  3. Modellerimin yöntem ve özelliklerinin belirleyici olmadığında ve yan etkiler geliştirdiğinde her zaman fark etmiyorum.

İşte basit bir örnek. İlk başta, Usermodel şöyleydi:

class User(db.Models):

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

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

Zamanla, buna dönüştü:

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

Ne istiyorum benim kodunda varlıkları ayırmaktır:

  1. Veritabanımın varlıkları, veritabanı seviyesi: Uygulamam ne içerir?
  2. Uygulamamın varlıkları, iş mantığı seviyesi: Başvurum ne yapabilir?

Django'da uygulanabilecek böyle bir yaklaşımı uygulamak için iyi uygulamalar nelerdir?


14
Sinyaller hakkında bilgi edinin
Konstant

1
etiketi kaldırdınız ancak sistemin ne yaptığını (işlevsellik) ve sistemin ne olduğunu (veri / etki alanı modeli) ayırmak için DCI kullanabilirsiniz
Rune FS

2
Tüm iş mantığını sinyal geri aramalarına uygulamayı mı öneriyorsunuz? Ne yazık ki, tüm uygulamam veritabanındaki olaylara bağlanamaz.
defuz

Rune FS, DCI'yi kullanmaya çalıştım, ancak projem için çok fazla şeye ihtiyaç duymadığı göründü: Bağlam, nesnelerin mixin olarak rollerin tanımı, vb. dır-dir"? Minimal bir örnek verebilir misiniz?
defuz

Yanıtlar:


635

Veri modeli ve etki alanı modeli arasındaki farkı soruyorsunuz gibi görünüyor - ikincisi, son kullanıcı tarafından algılanan iş mantığını ve varlıkları bulabileceğiniz yerdir, birincisi verilerinizi gerçekten depoladığınız yerdir.

Dahası, sorunuzun 3. bölümünü şu şekilde yorumladım: bu modelleri ayrı tutmanın nasıl başarısız olduğunu nasıl fark edersiniz?

Bunlar çok farklı iki kavramdır ve onları ayrı tutmak her zaman zordur. Bununla birlikte, bu amaç için kullanılabilecek bazı yaygın kalıplar ve araçlar vardır.

Alan Modeli Hakkında

Tanımanız gereken ilk şey, etki alanı modelinizin gerçekte verilerle ilgili olmamasıdır; o hakkındadır eylemler ve sorular "bu kullanıcının adının ne?" gibi, "Bu kullanıcıyı devre dışı", "Bu kullanıcının aktif hale" "kullanıcıların şu anda aktif hale gelir?" gibi ve. Klasik anlamda: sorgular ve komutlarla ilgilidir .

Komutlarda Düşünme

Örneğinizdeki komutlara bakarak başlayalım: "bu kullanıcıyı etkinleştir" ve "bu kullanıcıyı devre dışı bırak". Komutlarla ilgili güzel olan şey, o zaman verilen küçük senaryolarla kolayca ifade edilebilmeleridir:

Verilen aktif olmayan kullanıcı yönetici kullanıcının aktive sonra kullanıcı aktif hale gelir ve bir onay e-posta kullanıcıya gönderilir ve bir giriş sistemi günlüğüne eklenir (vs vs)




Bu tür senaryolar, altyapınızın farklı bölümlerinin tek bir komuttan nasıl etkilenebileceğini görmek için yararlıdır - bu durumda veritabanınız (bir tür 'etkin' bayrak), posta sunucunuz, sistem günlüğünüz, vb.

Bu tür senaryolar, Test Odaklı Geliştirme ortamı kurmanıza da gerçekten yardımcı olur.

Ve son olarak, komutları düşünmek gerçekten görev odaklı bir uygulama oluşturmanıza yardımcı olur. Kullanıcılarınız bunu takdir edecek :-)

Komutları İfade Etme

Django, komutları ifade etmenin iki kolay yolunu sunar; ikisi de geçerli seçeneklerdir ve iki yaklaşımı karıştırmak olağandışı değildir.

Hizmet katmanı

Servis modülü zaten edilmiştir @Hedde tarafından tarif . Burada ayrı bir modül tanımlarsınız ve her komut bir işlev olarak temsil edilir.

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

Formları kullanma

Diğer yol, her komut için bir Django Formu kullanmaktır. Bu yaklaşımı tercih ediyorum, çünkü yakından ilgili birçok yönü birleştiriyor:

  • komutun yürütülmesi (ne işe yarar?)
  • komut parametrelerinin doğrulanması (bunu yapabilir mi?)
  • komutun sunumu (bunu nasıl yapabilirim?)

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

Sorgularda Düşünme

Örneğin herhangi bir sorgu içermiyordu, bu yüzden birkaç yararlı sorgu oluşturma özgürlüğünü aldım. "Soru" terimini kullanmayı tercih ediyorum, fakat sorgular klasik terminolojidir. İlginç sorgular şunlardır: "Bu kullanıcının adı nedir?", "Bu kullanıcı giriş yapabilir mi?", "Devre dışı bırakılan kullanıcıların listesini göster" ve "Devre dışı bırakılan kullanıcıların coğrafi dağılımı nedir?"

Bu sorguları yanıtlamaya başlamadan önce, kendinize her zaman iki soru sormalısınız: Bu, yalnızca şablonlarım için bir sunum sorgusu ve / veya komutlarımı yürütmeye bağlı bir iş mantığı sorgusu ve / veya bir raporlama sorgusu mu?

Sunum sorguları yalnızca kullanıcı arayüzünü geliştirmek için yapılır. İş mantığı sorgularının yanıtları, komutlarınızın yürütülmesini doğrudan etkiler. Raporlama sorguları yalnızca analitik amaçlıdır ve daha gevşek zaman kısıtlamalarına sahiptir. Bu kategoriler birbirini dışlamaz.

Diğer soru şudur: "Cevaplar üzerinde tam kontrolüm var mı?" Örneğin, kullanıcının adını sorgularken (bu bağlamda), sonuç üzerinde herhangi bir kontrole sahip değiliz, çünkü harici bir API'ye güveniyoruz.

Sorgulama

Django'daki en temel sorgu Manager nesnesinin kullanılmasıdır:

User.objects.filter(active=True)

Tabii ki, bu sadece veriler veri modelinizde gerçekten temsil ediliyorsa işe yarar. Bu her zaman böyle değildir. Bu durumlarda, aşağıdaki seçenekleri göz önünde bulundurabilirsiniz.

Özel etiketler ve filtreler

İlk alternatif yalnızca sunum olan sorgular için kullanışlıdır: özel etiketler ve şablon filtreleri.

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)

Sorgu yöntemleri

Sorgunuz yalnızca sunum amaçlı değilse , services.py (eğer kullanıyorsanız) sorgu ekleyebilir veya bir queries.py modülü tanıtabilirsiniz :

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 

Proxy modelleri

Proxy modelleri iş mantığı ve raporlama bağlamında çok faydalıdır. Temel olarak modelinizin gelişmiş bir alt kümesini tanımlarsınız. Manager.get_queryset()Yöntemi geçersiz kılarak bir Yöneticinin temel QuerySet'ini geçersiz kılabilirsiniz .

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

Sorgu modelleri

Doğal olarak karmaşık, ancak oldukça sık yürütülen sorgular için, sorgu modelleri olasılığı vardır. Bir sorgu modeli, tek bir sorgu için ilgili verilerin ayrı bir modelde saklandığı bir tür denormalizasyon şeklidir. İşin püf noktası, normalize edilmemiş modeli birincil modelle senkronize tutmaktır. Sorgu modelleri yalnızca değişiklikler tamamen sizin kontrolünüz altındaysa kullanılabilir.

models.py

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

İlk seçenek bu modelleri komutlarınızda güncellemektir. Bu modeller yalnızca bir veya iki komutla değiştirilirse çok kullanışlıdır.

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

Özel sinyalleri kullanmak daha iyi bir seçenek olabilir. Bu sinyaller elbette komutlarınız tarafından yayılır. Sinyaller, birden çok sorgu modelini orijinal modelinizle senkronize halde tutabilmeniz avantajına sahiptir. Ayrıca, sinyal işleme Kereviz veya benzer çerçeveler kullanılarak arka plan görevlerine yüklenebilir.

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

Temiz tutulması

Bu yaklaşımı kullanırken, kodunuzun temiz kalıp kalmadığını belirlemek gülünç kolaylaşır. Sadece şu yönergeleri izleyin:

  • Modelim veritabanı durumunu yönetmekten daha fazlasını yapan yöntemler içeriyor mu? Bir komut çıkarmalısınız.
  • Modelim veritabanı alanlarına eşlenmeyen özellikler içeriyor mu? Bir sorgu çıkarmalısınız.
  • Modelim veritabanım olmayan altyapıyı (posta gibi) referans alıyor mu? Bir komut çıkarmalısınız.

Aynı şey görüşler için de geçerlidir (çünkü görünümler genellikle aynı problemden muzdariptir).

  • Benim görüşüm veritabanı modellerini aktif olarak yönetiyor mu? Bir komut çıkarmalısınız.

Bazı Referanslarımız

Django belgeleri: proxy modelleri

Django belgeleri: sinyaller

Mimari: Etki Alanına Dayalı Tasarım


11
DDD'yi django ile ilgili bir soruya dahil eden bir cevap görmek güzel. Django'nun ActiveRecord'u kalıcılık için kullanması, endişelerin ayrılmasının pencereden çıkması gerektiği anlamına gelmez. Mükemmel cevap.
Scott Coates

6
Looged kullanıcının bir nesneyi silmeden önce bir nesnenin sahibi olduğunu doğrulamak istersem, bunu görünümde veya form / hizmet modülünde kontrol etmeli miyim?
Ivan

6
@Ivan: her ikisi. Bu gerekir o iş kısıtlamaları parçası olduğu için formu / servis modülüne olmak. Bu gerektiğini yalnızca mevcut eylemleri kullanıcıların gerçekten yürütebileceği gerektiğini, çünkü aynı zamanda görünümünde olacak.
publysher

4
Özel yöneticisi yöntemleri sorguları uygulamak için iyi bir yoldur: User.objects.inactive_users(). Ancak burada IMO'nun proxy modeli örneği yanlış semantiğe yol açar: u = InactiveUser.objects.all()[0]; u.active = True; u.save()ve henüz isinstance(u, InactiveUser) == True. Ayrıca birçok durumda bir db görünümü ile bir sorgu modeli korumak için etkili bir yol söz.
Aryeh Leib Taurog

1
@adnanmuttaleb Bu doğru. Yanıtın yalnızca "Etki Alanı Modeli" terimini kullandığını unutmayın. Cevabım DDD olduğu için değil, bu kitabın etki alanı modelleri hakkında düşünmenize yardımcı olmak için harika bir iş yaptığı için DDD bağlantısını ekledim.
publysher

148

Genellikle görünümler ve modeller arasında bir hizmet katmanı uygularım. Bu, projenizin API'sı gibi davranır ve neler olup bittiğine dair iyi bir helikopter görünümü sağlar. Bu uygulamayı Java projeleri (JSF) ile bu katmanlama tekniğini çok kullanan bir meslektaşımdan miras aldım, örneğin:

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

Dikkat edin, genellikle modelleri, görüşleri ve hizmetleri modül seviyesine alıyorum ve projenin boyutuna bağlı olarak daha da ayrıyorum


8
Genel yaklaşımdan hoşlanıyorum, ancak benim anlayışımdan özel örneğiniz genellikle yönetici olarak uygulanacaktır .
arie

9
@arie mutlaka, belki daha iyi bir örnek, bir Webshop hizmetleri, sepeti oturumları, ürün derecelendirme hesaplamaları gibi asenkron görevleri üreten oluşturma ve vesaire e-posta göndermek gibi özellikleri de içermelidir için
Hedde van Heide der

4
Java'dan da gelen bu yaklaşımı da seviyorum. Python'da yeniyim, views.py'yi nasıl test edersiniz? Hizmet katmanıyla nasıl alay edersiniz (örneğin, hizmet bazı uzak api çağrıları yaparsa)?
Teimuraz

71

Her şeyden önce, kendini tekrar etme .

O zaman, lütfen mühendisliği yapmamaya dikkat edin, bazen sadece zaman kaybıdır ve birisinin önemli olana odaklanmasını kaybeder. İnceleme pitonun zen zaman zaman.

Aktif projelere bir göz atın

  • daha fazla kişi = daha düzgün bir şekilde organize etme ihtiyacı
  • Django depoya onlar basit bir yapıya sahiptir.
  • pip depo onlar straigtforward dizin yapısına sahiptir.
  • Kumaş deposu da bakmak iyi biridir.

    • tüm modellerinizi altına yerleştirebilirsiniz yourapp/models/logicalgroup.py
  • ör User. Groupve ilgili modelleryourapp/models/users.py
  • örneğin Poll, Question, Answer... altında gidebilirizyourapp/models/polls.py
  • __all__içine ihtiyacınız olanı yükleyinyourapp/models/__init__.py

MVC hakkında daha fazla bilgi

  • model verilerinizdir
    • bu gerçek verilerinizi içerir
    • buna oturum / çerez / önbellek / fs / dizin verileriniz de dahildir
  • kullanıcı modeli manipüle etmek için kontrolörle etkileşime girer
    • bu bir API veya verilerinizi kaydeden / güncelleyen bir görünüm olabilir
    • bu request.GET/ request.POST... vs ile ayarlanabilir
    • sayfalamayı veya filtrelemeyi de düşünün .
  • veriler görünümü günceller
    • şablonlar verileri alır ve buna göre biçimlendirir
    • Şablonlar olmadan bile API'ler görünümün bir parçasıdır; örneğin tastypieveyapiston
    • bu ayrıca ara katman yazılımını da hesaba katmalıdır.

Ara katman yazılımlarından / şablon etiketlerinden yararlanın

  • Her istek için yapılması gereken bazı işler varsa, ara katman yazılımı gitmenin bir yoludur.
    • örneğin zaman damgaları ekleme
    • ör. sayfa isabetleriyle ilgili metrikleri güncelleme
    • örneğin önbellek doldurmak
  • Nesneleri biçimlendirmek için her zaman tekrarlayan kod snippet'leriniz varsa, şablon etiketleri iyidir.
    • etkin sekme / url kırıntıları

Model yöneticilerinden faydalanın

  • oluşturma bir Usergidebilirsiniz UserManager(models.Manager).
  • örnekler için kanlı ayrıntılar devam etmelidir models.Model.
  • için kanlı ayrıntılar querysetgidebilir models.Manager.
  • Userher seferinde bir tane oluşturmak isteyebilirsiniz , bu yüzden modelin kendisinde yaşaması gerektiğini düşünebilirsiniz, ancak nesneyi oluştururken muhtemelen tüm ayrıntılara sahip değilsiniz:

Misal:

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

Mümkün olduğunca formları kullanın

Bir modelle eşleşen formlarınız varsa çok sayıda demirbaş kodu ortadan kaldırılabilir. ModelForm documentationOldukça iyidir. Formlar için kodları model kodundan ayırmak, çok fazla özelleştirmeniz varsa (veya bazen daha gelişmiş kullanımlar için döngüsel içe aktarma hatalarından kaçının) iyi olabilir.

Mümkünse yönetim komutlarını kullanın

  • Örneğin yourapp/management/commands/createsuperuser.py
  • Örneğin yourapp/management/commands/activateinbulk.py

iş mantığınız varsa, onu ayırabilirsiniz

  • django.contrib.auth arka uçları kullanır , tıpkı db'nin bir arka ucu olduğu gibi ... vb.
  • settingiş mantığınız için bir ekleyin (ör. AUTHENTICATION_BACKENDS)
  • kullanabilirsin django.contrib.auth.backends.RemoteUserBackend
  • kullanabilirsin yourapp.backends.remote_api.RemoteUserBackend
  • kullanabilirsin yourapp.backends.memcached.RemoteUserBackend
  • zor iş mantığını arka uca devretmek
  • giriş / çıkışta beklentiyi doğru ayarladığınızdan emin olun.
  • iş mantığını değiştirmek bir ayarı değiştirmek kadar basit :)

arka uç örneği:

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' 

olabilir:

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

tasarım desenleri hakkında daha fazla bilgi

arayüz sınırları hakkında daha fazla bilgi

  • Kullanmak istediğiniz kod gerçekten modellerin bir parçası mı? ->yourapp.models
  • Kod iş mantığının bir parçası mı? ->yourapp.vendor
  • Kod genel araçların / kütüphanelerin bir parçası mı? ->yourapp.libs
  • Kod iş mantığı kütüphanelerinin bir parçası mı? -> yourapp.libs.vendorveyayourapp.vendor.libs
  • İşte iyi bir tane: kodunuzu bağımsız olarak test edebilir misiniz?
    • Evet iyi :)
    • hayır, arayüz probleminiz olabilir
    • açık bir ayrılık olduğunda, birim test alaycı kullanımla bir esinti olmalıdır
  • Ayrılma mantıklı mı?
    • Evet iyi :)
    • Hayır, bu mantıksal kavramları ayrı ayrı test etmekte sorun yaşayabilirsiniz.
  • 10 kat daha fazla kod aldığınızda yeniden düzenleme yapmanız gerekeceğini düşünüyor musunuz?
    • evet, hayır iyi, hayır bueno, refactor çok iş olabilir
    • hayır, bu sadece harika!

Kısacası,

  • 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

veya size yardımcı olan herhangi bir şey; ihtiyacınız olan arayüzleri ve sınırları bulmak size yardımcı olacaktır.


27

Django biraz değiştirilmiş bir MVC kullanır. Django'da "kontrolör" kavramı yok. En yakın proxy, MVC dönüştürmeleriyle karışıklığa neden olma eğiliminde olan bir "görünüm" dür, çünkü MVC'de bir görünüm daha Django'nun "şablonu" na benzer.

Django'da "model" yalnızca bir veritabanı soyutlaması değildir. Bazı açılardan, görevi Django'nun MVC'nin denetleyicisi olarak "görüşü" ile paylaşır. Bir örnekle ilişkili tüm davranışları barındırır. Bu örneğin, davranışının bir parçası olarak harici bir API ile etkileşime girmesi gerekiyorsa, bu yine de model kodudur. Aslında, modellerin veritabanıyla hiç etkileşime girmesi gerekmez, bu nedenle harici bir API için tamamen etkileşimli bir katman olarak var olan modellere sahip olabilirsiniz. Çok daha özgür bir "model" kavramı.


7

Django'da, MVC yapısı Chris Pratt'in söylediği gibi, diğer çerçevelerde kullanılan klasik MVC modelinden farklı olarak, bunu yapmanın ana nedeninin CakePHP gibi diğer MVC çerçevelerinde olduğu gibi çok katı bir uygulama yapısından kaçınmak olduğunu düşünüyorum.

Django'da MVC şu şekilde uygulandı:

Görünüm katmanı ikiye bölünür. Görünümler yalnızca HTTP isteklerini yönetmek için kullanılmalı, çağrılmalı ve yanıtlanmalıdır. Görünümler uygulamanızın geri kalanıyla iletişim kurar (basit durumlarda doğrudan modellerle formlar, model formları, özel sınıflar). Arabirimi oluşturmak için Şablonlar kullanıyoruz. Şablonlar Django'ya dizeye benzer, bir bağlamı bunlarla eşleştirir ve bu bağlam uygulama tarafından görünüme iletilir (görünüm istendiğinde).

Model katmanı, kapsülleme, soyutlama, doğrulama, zeka verir ve verilerinizi nesne yönelimli hale getirir (bir gün DBMS'nin de yapacağını söylerler). Bu, büyük models.py dosyaları yapmanız gerektiği anlamına gelmez (aslında çok iyi bir tavsiye, modellerinizi farklı dosyalara bölmek, 'modeller' adlı bir klasöre koymak, '__init__.py' dosyası yapmak tüm modellerinizi içe aktardığınız ve son olarak model.Model sınıfının 'app_label' özelliğini kullandığınız bir klasör). Model, verilerle çalışmanızı soyutlamalı, uygulamanızı basitleştirecektir. Gerekirse, modelleriniz için "araçlar" gibi harici sınıflar da oluşturmalısınız. Modelinizde mirası kullanarak modelinizin Meta sınıfının 'soyut' özelliğini 'True' olarak ayarlayabilirsiniz.

Gerisi nerede? Küçük web uygulamaları genellikle verilere bir arayüzdür, bazı küçük programlarda veri sorgulamak veya eklemek için görünümleri kullanmak yeterli olacaktır. Daha yaygın durumlarda, aslında "denetleyici" olan Formlar veya ModelForms kullanılır. Bu, ortak bir soruna pratik bir çözümden başka bir şey değildir ve çok hızlı bir sorundur. Bir web sitesi bunu yapmak için kullanır.

Formlar sizin için yeterli değilse, sihri yapmak için kendi sınıflarınızı oluşturmanız gerekir, bunun çok iyi bir örneği admin uygulamasıdır: ModelAmin kodunu okuyabilirsiniz, bu aslında bir denetleyici olarak çalışır. Standart bir yapı yoktur, mevcut Django uygulamalarını incelemenizi öneririm, her duruma bağlıdır. Django geliştiricilerinin amaçladığı şey, xml ayrıştırıcı sınıfı, API bağlayıcı sınıfı ekleyebilir, görevleri gerçekleştirmek için Kereviz ekleyebilir, reaktör tabanlı bir uygulama için bükülmüş, sadece ORM'yi kullanabilir, bir web hizmeti yapabilir, yönetici uygulamasını değiştirebilir ve daha fazlasını yapabilirsiniz. .. Kaliteli kod yapmak, MVC felsefesine saygı duymak ya da vermemek, modül tabanlı yapmak ve kendi soyutlama katmanlarınızı oluşturmak sizin sorumluluğunuzdur. Çok esnektir.

Benim tavsiyem: olabildiğince çok kod okuyun, etrafında çok sayıda django uygulaması var, ama onları çok ciddiye almayın. Her vaka farklıdır, desenler ve teori yardımcı olur, ancak her zaman değil, bu kesin olmayan bir cinstir, django size sadece bazı ağrıları ortadan kaldırmak için kullanabileceğiniz iyi araçlar sağlar (yönetici arayüzü, web formu doğrulama, i18n, gözlemci desen uygulaması, hepsi gibi) Daha önce bahsedilenler ve diğerleri), ancak iyi tasarımlar deneyimli tasarımcılardan gelir.

Not: auth uygulamasından (standart django'dan) 'Kullanıcı' sınıfını kullanın, örneğin kullanıcı profilleri oluşturabilir veya en azından kodunu okuyabilirsiniz, durumunuz için yararlı olacaktır.


1

Eski bir soru, ama yine de çözümümü sunmak istiyorum. Model nesnelerinin de bazı ek işlevler gerektirdiği kabul edilirken, models.py içine yerleştirmek gariptir . Ağır iş mantığı kişisel zevkinize bağlı olarak ayrı ayrı yazılabilir, ancak en azından modelin kendisiyle ilgili her şeyi yapmasını seviyorum. Bu çözüm, tüm mantığın modellerin içine yerleştirilmesini sevenleri de destekler.

Bu nedenle, mantığı model tanımlarından ayırmamı ve yine de tüm ipuçlarını IDE'mden almamı sağlayan bir kesmek tasarladım .

Avantajları açık olmalı, ancak bu gözlemlediğim birkaçını listeliyor:

  • DB tanımları sadece bu kalır - hiçbir mantık "çöp" bağlı
  • Modelle ilgili mantık tek bir yerde düzgün bir şekilde yerleştirilir
  • Tüm hizmetlerin (formlar, REST, görünümler) mantığa tek bir erişim noktası vardır
  • En iyisi: Modellerimin çok karmaşık olduğunu ve mantığı ayırmak zorunda kaldığını anladıktan sonra herhangi bir kodu yeniden yazmak zorunda kalmadım . Ayırma düzgün ve yinelemelidir: Bir seferde veya tüm sınıfta veya tüm models.py'de bir işlev yapabilirim.

Bunu Python 3.4 ve üstü ve Django 1.8 ve üstü ile kullanıyorum.

Uygulamanın / models.py

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

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

Uygulamanın / mantık / 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 ...

Anlayamadığım tek şey IDE'mi (bu durumda PyCharm) UserLogic'in aslında Kullanıcı modeli olarak nasıl tanıyacağıdır. Ama bu açıkçası bir hack olduğu için, selfparametre için her zaman tür belirtmenin küçük sıkıntısını kabul etmekten oldukça mutluyum .


Aslında bunu kullanımı kolay bir yaklaşım olarak görüyorum. Ama son modeli başka bir dosyaya taşırdım ve models.py'ye miras kalmazdım. Bu service.py gibi olurdu userlogic + modelini çatışma vardı
Maks

1

Seninle aynı fikirdeyim. Django'da çok fazla olasılık var, ancak başlamak için en iyi yer Django'nun tasarım felsefesini gözden geçirmek .

  1. Bir model özelliğinden bir API çağırmak ideal olmazdı, görünüşte böyle bir şey yapmak daha mantıklı olacak ve muhtemelen işleri kuru tutmak için bir hizmet katmanı oluşturacak gibi görünüyor. API'ya yapılan çağrı engellemiyorsa ve çağrı pahalıysa, isteği bir servis çalışanına (kuyruktan çalışan bir çalışan) göndermek mantıklı olabilir.

  2. Django'nun tasarım felsefesi modellerine göre, bir "nesnenin" her yönünü kapsamaktadır. Dolayısıyla, bu nesne ile ilgili tüm iş mantığı orada yaşamalıdır:

Alakalı tüm alan adı mantığını dahil et

Modeller, Martin Fowler'in Aktif Kayıt tasarım modelini izleyerek bir “nesnenin” her yönünü kapsamalıdır.

  1. Açıkladığınız yan etkiler açıktır, buradaki mantık Querysets ve yöneticilere daha iyi bölünebilir. İşte bir örnek:

    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

Çoğunlukla seçilen yanıta katılıyorum ( https://stackoverflow.com/a/12857584/871392 ), ancak Sorgu Yapma bölümünde seçenek eklemek istiyorum.

Filtre sorguları yapmak için modeller için QuerySet sınıfları tanımlanabilir. Bundan sonra bu queryset sınıfını yerleşik Manager ve QuerySet sınıfları gibi model yöneticisi için proxy yapabilirsiniz.

Bir alan modeli almak için birkaç veri modelini sorgulamanız gerekiyorsa, bunu daha önce önerilen gibi ayrı bir modüle koymak benim için daha makul görünüyor.



-6

Django, web sayfalarını sunmak için titizlikle kullanılmak üzere tasarlanmıştır. Bu konuda rahat değilseniz belki de başka bir çözüm kullanmalısınız.

Kök veya ortak işlemleri modelde (aynı arayüze sahip olmak için) ve diğerlerini modelin denetleyicisinde yazıyorum. Başka bir modelden bir operasyona ihtiyacım olursa kontrol cihazını içeri aktarırım.

Bu yaklaşım benim için ve uygulamalarımın karmaşıklığı için yeterli.

Hedde'nin yanıtı, django ve python'un esnekliğini gösteren bir örnektir.

Zaten çok ilginç bir soru!


9
Sorunuzu anlamama yardımcı olmanız için yeterince iyi olduğunu nasıl söylüyorsunuz?
Chris Wesseling

1
Django'nun django.db.models dışında sunabileceği çok şey var, ancak ekosistemin çoğu django modellerini kullanarak modelinize bağlı.
andho

1
Yazılım geliştirmek için kullanılan tasarım deseni. Ve sadece web sayfalarını değil, orta veya büyük ölçekte yazılım sunmak için kolayca kullanılmak üzere tasarlanmış django!
Mohammad Torkashvand
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.