2010-12-13 22 views
11

(Django 1.1)私は、m2mフィールドを使ってメンバを追跡するProjectモデルを持っています。それは次のようになります。Django - post_saveシグナル経由でm2mデータを保存するには?

class Project(models.Model): 
    members = models.ManyToManyField(User) 
    sales_rep = models.ForeignKey(User) 
    sales_mgr = models.ForeignKey(User) 
    project_mgr = models.ForeignKey(User) 
    ... (more FK user fields) ... 

プロジェクトが作成され、選択したsales_repsales_mgrproject_mgrなどUser Sはそれが簡単にプロジェクトの権限を追跡するために作るためにメンバーに追加されます。これまでのところ、このアプローチは非常にうまくいっています。

私が今対処している問題は、User FKフィールドの1つが管理者によって更新されたときにプロジェクトのメンバーシップを更新する方法です。私はこの問題への様々な解決策を試してみたが、クリーンなアプローチは、以下のようなpost_save信号であるように見えた:私は介して、フィールドの1つを変更しても

def update_members(instance, created, **kwargs): 
    """ 
    Signal to update project members 
    """ 
    if not created: #Created projects are handled differently 
     instance.members.clear() 

     members_list = [] 
     if instance.sales_rep: 
      members_list.append(instance.sales_rep) 
     if instance.sales_mgr: 
      members_list.append(instance.sales_mgr) 
     if instance.project_mgr: 
      members_list.append(instance.project_mgr) 

     for m in members_list: 
      instance.members.add(m) 
signals.post_save.connect(update_members, sender=Project) 

しかし、Projectはまだ同じメンバーを持っています管理者!私は他のプロジェクトで自分のビューを使ってメンバーのm2mフィールドを更新して成功しましたが、管理者とのやりとりがうまくいくようにする必要はありませんでした。

メンバーシップを更新するためにpost_save信号以外の方法がありますか?あなたの助けを前もってありがとう!

UPDATE:

だけ明確にする私は(古いメンバーが削除され、新しいものが追加されている)フロントエンドで自分のフォームを保存するとき、post_save信号が正常に動作します。しかし、adminを介してプロジェクトを保存すると、post_save信号が正しく機能しません(メンバーは同じままです)。

私はPeter Rowellの診断がこの状況で正しいと思います。 adminフォームから「members」フィールドを削除すると、post_save信号が正しく機能します。フィールドがインクルードされると、保存時にフォームに存在する値に基づいて古いメンバーが保存されます。プロジェクトが保存されるとき(信号またはカスタム保存メソッドであろうと)メンバーm2mフィールドにどのような変更を加えても、保存前にフォームに存在していたメンバーによって常に上書きされます。それを指摘してくれてありがとう!

+2

これはあなたの問題であれば、私は知らないが、私はあなたがどのようにフォームのコードのアーティファクトの中に実行されて勘を持っている:サヴェリオの答えに続き

、次のコードは、私の問題を解決しましたm2m infoを更新します。基本的には最初にメインオブジェクトを保存してからm2mの値をすべてクリアし、*の形式で*存在する値に基づいて設定します。これはメインオブジェクトのsave()の後で*発生するので、save()や 'post_save'シグナルに基づいて何かをやってから、*元に戻します*。これは 'django.forms.models.save_instance()'にあります。 'after_form_save'シグナルがあればいいでしょう。 –

+0

ありがとう、ピーター!私はあなたの診断が正しいと信じています。この情報を含めるためにオリジナルの投稿を更新しました。 –

+0

ピーターは正しいです。私は同じ問題を抱えていましたが、回避策を見つけましたが、 'after_form_save'シグナルとしてきれいではありません:http://stackoverflow.com/questions/3652585/simple-django-form-model-save-question –

答えて

4

あなたのコードに間違いはありませんが、他のどのアプリとも異なる管理が必要と思われるのは混乱しています。

しかし、私はあなたのモデル構造が間違っていると言わなければなりません。私はあなたがすべてのそれらのForeignKeyフィールドを取り除く必要があると思うだけManyToManyを持って - しかし、役割を追跡するためにスルーテーブルを使用します。

class Project(models.Model): 
    members = models.ManyToManyField(User, through='ProjectRole') 

class ProjectRole(models.Model): 
    ROLES = (
     ('SR', 'Sales Rep'), 
     ('SM', 'Sales Manager'), 
     ('PM', 'Project Manager'), 
    ) 
    project = models.ForeignKey(Project) 
    user = models.ForeignKey(User) 
    role = models.CharField(max_length=2, choices=ROLES) 
+0

私はモデル構造を改良する必要があることに同意しますが、私は古い実装で作業しており、それを最大限に活用しようとしています。現時点では、システムをこの新しい構造に移行する準備はできていませんが、今後のご提案をお待ちしています。ありがとう。 –

6

私の解決策は、m2m_changed信号を使用することです。次の例のように、2つの場所で使用できます。

  • がpre_clear
  • 明確な関係
  • を発する:

    • は、各M2M用post_save信号
    • を発するモデルフィールドを保存します。に

      省の際に管理者が続行されます

    • はpost_clearを返します
    • は再び
    • は、あなたが実際にそれを保存する前に保存したデータの内容を変更する簡単な例を持っている。ここ

post_add発する

  • 移入をpre_add発します。

    class MyModel(models.Model): 
    
        m2mfield = ManyToManyField(OtherModel) 
    
        @staticmethod 
        def met(sender, instance, action, reverse, model, pk_set, **kwargs): 
         if action == 'pre_add': 
          # here you can modify things, for instance 
          pk_set.intersection_update([1,2,3]) 
          # only save relations to objects 1, 2 and 3, ignoring the others 
         elif action == 'post_add': 
          print pk_set 
          # should contain at most 1, 2 and 3 
    
    m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through) 
    

    またpre_removepost_removepre_clearpost_clearに耳を傾けることができます。

    def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs): 
        """ Ensures that the active services are a subset of the enabled ones. 
        """ 
        if action == 'pre_add' and sender == Account.active_services.through: 
         # remove from the selection the disabled ones 
         pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True)) 
        elif action == 'pre_clear' and sender == Account.enabled_services.through: 
         # clear everything 
         instance._cache_active_services = list(instance.active_services.values_list('id', flat=True)) 
         instance.active_services.clear() 
        elif action == 'post_add' and sender == Account.enabled_services.through: 
         _cache_active_services = getattr(instance, '_cache_active_services', None) 
         if _cache_active_services: 
          instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services))) 
          delattr(instance, '_cache_active_services') 
        elif action == 'pre_remove' and sender == Account.enabled_services.through: 
         # de-default any service we are disabling 
         instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set))) 
    

    「有効」場合:私の場合は、リストが保存されている順序とは無関係に、別の(「有効なもの」)の内容内(「アクティブなもの」)1つのリストをフィルタリングするためにそれらを使用しています(pre_clear)でキャッシュされクリアされ、次に2回目のパス( 'post_add')の後にキャッシュから追加されます。 。

    トリックは、もう一方のm2m_changed信号の1つのリストを更新することでした。

  • +0

    あなたは私の一日を救った!ありがとう:) –

    0

    私は、m2m_field経由でモデルに接続されたアイテムのセットから最新のアイテムを見つける必要があったときに立ち往生しました。

    def update_item(sender, instance, action, **kwargs): 
        if action == 'post_add': 
         instance.related_field = instance.m2m_field.all().order_by('-datetime')[0] 
         instance.save() 
    
    m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through) 
    
    関連する問題