web-dev-qa-db-ja.com

djangoにおけるビジネスロジックとデータアクセスの分離

私はDjangoでプロジェクトを書いていますが、コードの80%がmodels.pyファイルに入っています。このコードは紛らわしいので、しばらくすると、実際に何が起きているのか理解できなくなります。

これが私を悩ますものです:

  1. 私のモデルレベル(データベースからのデータを扱う作業にのみ責任があるとされていた)もまた電子メールを送ったり、他のサービスへのAPIを歩いたりしている、などというのは曖昧です.
  2. また、ビジネスロジックをビューに含めることは受け入れられないと思います。この方法では制御が困難になります。たとえば、私のアプリケーションではUserの新しいインスタンスを作成する方法が少なくとも3つありますが、技術的にはそれらを統一的に作成する必要があります。
  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に適用できるそのようなアプローチを実装するための良い習慣は何ですか?

417
defuz

データモデルドメインモデルの違いについて質問しているようです - 後者がビジネスロジックとエンティティを知覚できる場所にあります。あなたのエンドユーザーによって、前者はあなたが実際にあなたのデータを保存するところです。

さらに、私はあなたの質問の第3部を次のように解釈しました:これらのモデルを別々にしておくことの失敗に気づく方法。

これらは2つの非常に異なる概念であり、それらを別々にしておくことは常に困難です。ただし、この目的に使用できる一般的なパターンとツールはいくつかあります。

ドメインモデルについて

最初に認識する必要があるのは、ドメインモデルは実際にはデータに関するものではないということです。actionsおよびquestions「このユーザーをアクティブにする」、「このユーザーを非アクティブにする」、「現在どのユーザーをアクティブにしていますか」、「何」などです。このユーザーの名前は? "古典的に言えば、それはquerycommandsについてです。

コマンドで考える

例の中のコマンド「このユーザーをアクティブにする」と「このユーザーを非アクティブにする」を見てみましょう。コマンドのいいところは、それらが小さな与えられた時のシナリオで簡単に表現できることです。

与えられた非アクティブなユーザー
管理者がこのユーザーをアクティブにしたとき
次にユーザーがアクティブになります
および確認の電子メールがユーザーに送信されます
およびエントリがシステムログに追加されます
(など)

このようなシナリオは、インフラストラクチャのさまざまな部分が1つのコマンドで影響を受ける可能性があることを確認するのに役立ちます。この場合、データベース(ある種の「アクティブ」フラグ)、メールサーバー、システムログなどです。

このようなシナリオは、テスト駆動開発環境の設定にも役立ちます。

そして最後に、コマンドで考えることは本当にあなたがタスク指向のアプリケーションを作成するのを助けます。あなたのユーザーはこれを理解するでしょう:-)

表現コマンド

Djangoはコマンドを表現する2つの簡単な方法を提供します。どちらも有効な選択肢であり、2つのアプローチを組み合わせることは珍しいことではありません。

サービス層

サービスモジュールはすでに @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

クエリで考える

あなたの例にはクエリが含まれていなかったので、私はいくつかの有用なクエリを構成することの自由を取った。私は「質問」という用語を使うことを好みますが、クエリは古典的な用語です。 「このユーザーの名前は何ですか?」、「このユーザーはログインできますか」、「非アクティブユーザーのリストを表示しますか」、および「非アクティブユーザーの地理的分布は?」

これらの質問に答えることを始める前に、あなたは常に2つの質問をするべきです:これは私のテンプレートのためだけのpresentational質問、そして/またはbusiness logicquery私のコマンドやreportingクエリを実行することに関係しています。

プレゼンテーションクエリは単にユーザインタフェースを改善するために行われます。ビジネスロジッククエリに対する回答は、コマンドの実行に直接影響します。レポートクエリは単に分析目的のためのものであり、時間的制約が緩やかです。これらのカテゴリは相互に排他的ではありません。

もう1つの質問は、「答えを完全に制御することはできますか?」です。たとえば、(このコンテキストでは)ユーザーの名前を照会するときは、外部APIに依存しているため、結果を制御することはできません。

問い合わせをする

Djangoで最も基本的なクエリはManagerオブジェクトの使用です。

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にクエリを追加するか(あるいはそれを使っているなら)、またはquery.pyモジュール:

query.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)

最初のオプションはあなたのコマンドでこれらのモデルを更新することです。これらのモデルが1つか2つのコマンドによってのみ変更される場合、これは非常に役に立ちます。

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

より良い選択肢は、カスタム信号を使うことです。これらのシグナルはもちろんあなたのコマンドによって発せられます。シグナルには、複数のクエリモデルを元のモデルと同期させることができるという利点があります。さらに、Celeryまたは同様のフレームワークを使用して、信号処理をバックグラウンドタスクにオフロードできます。

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ドキュメント:シグナル

アーキテクチャ:ドメイン駆動設計

546
publysher

私は通常、ビューとモデルの間にサービス層を実装します。これはあなたのプロジェクトのAPIのように振る舞い、何が起こっているのかについてのヘリコプターの良い見方をあなたに与えます。私はこのプラクティスを、Javaプロジェクト(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 """
    if limit:
        return Book.objects.filter(**filters)[:limit]
    return Book.objects.filter(**filters)

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

あなたの心に、私は通常、モジュールレベルにモデル、ビューやサービスを取り、プロジェクトの規模に応じてさらに分離する

128

まず、 自分自身を繰り返さないでください

次に、過剰なエンジニアリングを行わないように注意してください。時には時間の無駄であり、誰かが重要なことに集中できなくなることがあります。 zen of python を時々確認してください。

アクティブなプロジェクトを見てください

  • より多くの人=より適切に整理する必要がある
  • Djangoリポジトリ は単純な構造です。
  • pip repository straigtforwardディレクトリ構造を持っています。
  • ファブリックリポジトリ も参照してください。

    • すべてのモデルをyourapp/models/logicalgroup.pyの下に配置できます
  • 例:UserGroupおよび関連モデルはyourapp/models/users.pyの下に配置できます
  • 例:PollQuestionAnswer ...はyourapp/models/polls.pyの下に配置できます
  • __all__内のyourapp/models/__init__.pyに必要なものをロードします

MVCの詳細

  • モデルはあなたのデータです
    • これには実際のデータが含まれます
    • これには、セッション/ Cookie /キャッシュ/ fs /インデックスデータも含まれます
  • ユーザーはコントローラーと対話してモデルを操作します
    • これは、API、またはデータを保存/更新するビューである可能性があります
    • これはrequest.GET/request.POST ... etcで調整できます
    • ページングまたはフィルタリングも考えてください。
  • データはビューを更新します
    • テンプレートはデータを取得し、それに応じてフォーマットします
    • APIはテンプレートなしでもビューの一部です。例えばtastypieまたはpiston
    • これもミドルウェアを説明する必要があります。

ミドルウェア / templatetagsを活用する

  • リクエストごとに何らかの作業を行う必要がある場合、ミドルウェアを使用する方法があります。
    • 例えばタイムスタンプの追加
    • 例えばページヒットに関するメトリックの更新
    • 例えばキャッシュへの投入
  • オブジェクトをフォーマットするために常に繰り返されるコードスニペットがある場合、templatetagsが適しています。
    • 例えばアクティブなタブ/ URLパンくずリスト

モデルマネージャーを活用する

  • Userを作成すると、UserManager(models.Manager)に移動できます。
  • インスタンスの厄介な詳細は、models.Modelにあるはずです。
  • querysetの詳細は、models.Managerに入れることができます。
  • Userを一度に1つずつ作成する必要がある場合があるため、モデル自体に存在する必要があると考えるかもしれませんが、オブジェクトを作成するとき、おそらくすべての詳細がありません。

例:

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
  • ここに良いものがあります:コードを独立してテストできますか?
    • いいね :)
    • いいえ、インターフェイスに問題がある可能性があります
    • 明確な分離がある場合、unittestは モックの使用
  • 分離は論理的ですか?
    • いいね :)
    • いいえ、これらの論理概念を個別にテストするのに苦労するかもしれません。
  • 10倍のコードを取得したときにリファクタリングする必要があると思いますか?
    • はい、ダメ、ブエノ、リファクタリングは大変な作業です
    • いいえ、それは最高です!

一言で言えば、あなたが持つことができる

  • 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

またはあなたを助ける他のもの;必要なインターフェイス境界を見つけるのが役立ちます。

65
dnozay

DjangoはMVCを少し修正したものを使っています。 Djangoには「コントローラ」という概念はありません。最も近いプロキシは "ビュー"です。これは、MVCではビューがDjangoの "テンプレート"に似ているため、MVC変換と混同しがちです。

Djangoでは、「モデル」は単なるデータベースの抽象化ではありません。いくつかの点で、それはMVCのコントローラとしてのDjangoの「見解」と義務を共有しています。インスタンスに関連した振る舞い全体を保持します。そのインスタンスがその動作の一部として外部APIと対話する必要がある場合でも、それはモデルコードです。実際、モデルはデータベースと対話する必要はまったくないため、外部APIに対する対話型レイヤーとして完全に存在するモデルがあると考えられます。それは「モデル」のはるかに自由な概念です。

24
Chris Pratt

Djangoでは、MVCの構造はChris Prattが言ったように、他のフレームワークで使われている古典的なMVCモデルとは異なり、これをする主な理由はCakePHPのような他のMVCフレームワークで起こるような厳しすぎるアプリケーション構造を避けることだと思います。

Djangoでは、MVCは次のように実装されていました。

ビューレイヤは2つに分割されています。ビューはHTTPリクエストを管理するためだけに使用されるべきであり、それらは呼び出されてそれらに応答します。ビューはアプリケーションの他の部分(フォーム、モデルフォーム、カスタムクラス、単純な場合はモデルと直接)と通信します。インターフェイスを作成するためにテンプレートを使います。テンプレートはDjangoにとって文字列のようなもので、コンテキストをそれらにマッピングし、このコンテキストはアプリケーションによってビューに伝えられました(ビューが要求したとき)。

モデル層はカプセル化、抽象化、検証、インテリジェンスを提供し、データをオブジェクト指向にします(いつかDBMSもそうするでしょう)。これはあなたが巨大なmodels.pyファイルを作るべきであることを意味するわけではありません(実際には非常に良いアドバイスはあなたのモデルを異なるファイルに分割し、それらを 'models'と呼ばれるフォルダに入れ、この中に '__init__.py'ファイルを作ることです)すべてのモデルをインポートし、最後にmodels.Modelクラスの属性 'app_label'を使用するフォルダ。モデルはデータを操作することからあなたを抽象化するべきです、それはあなたのアプリケーションをより簡単にするでしょう。また、必要に応じて、モデル用の「ツール」などの外部クラスを作成する必要があります。モデルのMetaクラスの 'abstract'属性を 'True'に設定して、モデルに継承を使用することもできます。

残りはどこにありますか?さて、小さなWebアプリケーションは一般的にデータへの一種のインタフェースです、いくつかの小さなプログラムのケースではデータを問い合わせたり挿入するためにビューを使用することで十分でしょう。より一般的なケースでは、FormsまたはModelFormを使用します。これらは実際には "コントローラ"です。これは一般的な問題に対する実用的な解決方法であり、非常に速いものではありません。それはウェブサイトがするのに使うことです。

Formsがあなたにとって不愉快でないなら、あなたは魔法をするためにあなた自身のクラスを作成するべきです、これの非常に良い例は管理アプリケーションです:あなたはModelAminコードを読むことができます、これは実際にコントローラとして働きます。標準的な構造はありません。既存のDjangoアプリを調べることをお勧めします。それぞれのケースによって異なります。これはDjango開発者が意図したことです。xmlパーサクラス、APIコネクタクラス、タスク実行用Celeryの追加、リアクタベースのアプリケーション用のツイスト、ORMのみの使用、Webサービスの作成、管理アプリケーションの変更などができます。 ..良質のコードを作成し、MVCの理念を尊重するかどうか、モジュールベースにして独自の抽象化レイヤを作成するのはあなたの責任です。とても柔軟です。

私の忠告:できるだけ多くのコードを読んでください、たくさんのDjangoアプリケーションがありますが、あまり真剣に考えないでください。それぞれのケースは異なり、パターンと理論は役に立ちますが、必ずしもそうではありませんが、これはあいまいな科学です。Djangoは、(管理者インターフェース、Webフォーム検証、国際化、オブザーバーパターン実装などの)前に述べたものや他のもの)、しかし良いデザインは経験豊富なデザイナーによるものです。

シモンズ:認証アプリケーションから(標準Djangoから)「ユーザー」クラスを使用すると、例えばユーザープロファイルを作成することができます、または少なくともそのコードを読む、それはあなたのケースに役立ちます。

5
Nate Gentile

私は主に選択された答え( https://stackoverflow.com/a/12857584/871392 )に同意しますが、[クエリの作成]セクションにオプションを追加します。

Makeフィルタクエリのためのモデル用のQuerySetクラスを定義することができます。その後、組み込みのManagerクラスやQuerySetクラスと同様に、このクエリセットクラスをモデルのマネージャに代入できます。

1つのドメインモデルを取得するために複数のデータモデルをクエリする必要がある場合は、これを以前に提案したように別のモジュールに配置する方が合理的です。

0
l0ki

私はあなたに同意しなければならないでしょう。 Djangoにはたくさんの可能性がありますが、始めるのに最適な場所は Djangoのデザイン哲学 です。

  1. モデルプロパティからAPIを呼び出すことは理想的ではないでしょう。ビュー内でこのようなことをして、おそらく物事をドライに保つためにサービスレイヤを作成するのがより理にかなっているようですAPIへの呼び出しがノンブロッキングで、呼び出しが高価なものである場合は、サービスワーカー(キューから消費するワーカー)にリクエストを送信することは意味があります。

  2. Djangoのデザイン哲学モデルは、「オブジェクト」のあらゆる側面をカプセル化しています。したがって、そのオブジェクトに関連するすべてのビジネスロジックはそこに存在するはずです。

すべての関連ドメインロジックを含めます

Martin FowlerのActive Recordデザインパターンに従って、モデルは「オブジェクト」のあらゆる側面をカプセル化する必要があります。

  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
radtek