2010-12-13 15 views
11

(Django 1.1) Ich habe ein Projektmodell, das seine Mitglieder über ein m2m-Feld verfolgt. Es sieht wie folgt aus:Django - Wie speichere ich m2m Daten über post_save Signal?

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

Wenn das Projekt erstellt wird, das ausgewählte sales_rep, sales_mgr, project_mgr, etc User s an die Mitglieder hinzugefügt werden, um es einfacher den Überblick über Projektberechtigungen zu halten. Dieser Ansatz hat bisher sehr gut funktioniert.

Das Problem, mit dem ich es jetzt zu tun habe, ist, wie man die Mitgliedschaft des Projekts aktualisiert, wenn eines der User FK-Felder über den Admin aktualisiert wird. Ich habe verschiedene Lösungen für dieses Problem versucht, aber der sauberste Ansatz schien ein post_save Signal wie das zu sein, folgende:

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) 

jedoch die Project hat immer noch die gleichen Elemente, auch wenn ich eines der Felder verändern, über die Administrator! Ich hatte Erfolg beim Aktualisieren der m2m-Felder der Mitglieder, indem ich meine eigenen Ansichten in anderen Projekten verwendete, aber ich musste nie dafür sorgen, dass es auch mit dem Admin gut spielte.

Gibt es einen anderen Ansatz, den ich anders als ein post_save-Signal nehmen sollte, um die Mitgliedschaft zu aktualisieren? Vielen Dank im Voraus für Ihre Hilfe!

UPDATE:

Nur um zu klären, das post_save Signal richtig funktioniert, wenn ich meine eigene Form im vorderen Ende (alte Mitglieder entfernt und neue hinzugefügt) speichern. Das post_save-Signal funktioniert jedoch NICHT korrekt, wenn ich das Projekt über den Admin speichere (Mitglieder bleiben gleich).

Ich denke, dass Peter Rowells Diagnose in dieser Situation richtig ist. Wenn ich das Feld "Mitglieder" aus dem Admin-Formular entferne, funktioniert das Signal post_save korrekt. Wenn das Feld eingeschlossen ist, speichert es die alten Mitglieder basierend auf den Werten, die zum Zeitpunkt des Speicherns im Formular vorhanden sind. Unabhängig davon, welche Änderungen ich am membermember-Feld members mache, wenn das Projekt gespeichert wird (ob es sich um eine Signal- oder eine benutzerdefinierte Speichermethode handelt), wird es immer von den Mitgliedern überschrieben, die vor dem Speichern im Formular vorhanden waren. Danke, dass du das unterstrichen hast!

+2

Ich weiß nicht, ob dies Ihr Problem ist, aber ich habe ein Bauchgefühl, dass Sie in ein Artefakt, wie die Formulare Code ausgeführt werden kann, aktualisiert m2m info. Im Grunde speichern sie zunächst das Hauptobjekt, dann setzen sie die m2m-Werte, indem sie zuerst alle löschen und dann basierend auf den Werten * in der Form * setzen. Dies geschieht * nach * dem save() auf dem Hauptobjekt, also wird alles, was Sie im save() oder auf dem 'post_save' Signal machen, erst erledigt und dann * rückgängig gemacht *. Dies ist in 'django.forms.models.save_instance()'. Es wäre schön, wenn es ein 'after_form_save'-Signal gäbe. –

+0

Danke, Peter! Ich glaube, deine Diagnose ist richtig. Ich habe meinen ursprünglichen Beitrag aktualisiert, um diese Informationen hinzuzufügen. –

+0

Peter hat Recht. Ich hatte das gleiche Problem und einen Workaround gefunden, aber es ist nicht ein ordentliches als 'after_form_save' Signal: http://StackOverflow.com/Questions/3652585/Simple-Django-Form-Model-Save-Question –

Antwort

4

Ich kann nichts falsch mit Ihrem Code sehen, aber ich bin verwirrt, warum Sie denken, dass der Admin anders als jede andere App funktionieren sollte.

Allerdings muss ich sagen, ich denke, dass Ihre Modellstruktur falsch ist. Ich denke, Sie müssen all diese ForeignKey-Felder loswerden und nur eine ManyToMany haben - aber verwenden Sie eine Durchgangs-Tabelle, um die Rollen zu verfolgen.

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

Ich stimme zu, dass die Modellstruktur verbessert werden sollte, aber ich arbeite mit einer älteren Implementierung und versuche, das Beste daraus zu machen. Zu diesem Zeitpunkt bin ich nicht bereit, das System auf diese neue Struktur umzustellen, aber ich werde Ihren Vorschlag für die Zukunft berücksichtigen. Vielen Dank. –

6

Nach dem gleichen Problem, ist meine Lösung, das m2m_changed Signal zu verwenden. Sie können es an zwei Stellen verwenden, wie im folgenden Beispiel.

Der Admin beim Speichern wird fortfahren:

  • die Felder der post_save Signal
  • Modell speichern
  • für jede m2m emittieren:
    • pre_clear
    • klar die Beziehung
    • emittieren
    • emittieren post_clear
    • emit
    • bevölkern wieder
    • post_add emit

Hier können Sie ein einfaches Beispiel, die den Inhalt der gespeicherten Daten ändert, bevor sie tatsächlich dem Speichern 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) 

können Sie auch pre_remove, post_remove, pre_clear und post_clear hört. In meinem Fall habe ich sie verwende eine Liste (‚aktive Dinge‘) in den Inhalt eines anderen (‚aktiviert Dinge‘) unabhängig von der Reihenfolge zu filtern, in denen Listen gespeichert:

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

Wenn die „enabled“ die einen werden aktualisiert (gelöscht/entfernt + hinzugefügt, wie in admin), dann werden die "aktiven" im Cache zwischengespeichert und gelöscht ('pre_clear') und dann nach dem zweiten Durchlauf ('post_add') wieder hinzugefügt .

Der Trick bestand darin, eine Liste auf den m2m_changed-Signalen des anderen zu aktualisieren.

+0

Du hast meinen Tag gerettet! Vielen Dank :) –

0

Ich habe auf der Situation festgefahren, als ich das neueste Element aus der Menge der Elemente finden musste, die über m2m_field mit dem Modell verbunden waren.

Saverio Antwort Folgen, gelöst folgenden Code mein Problem:

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) 
Verwandte Themen