2009-03-25 7 views
7

Mam ForeignKey, który może być pusty w moim modelu, aby model luźne połączenie między modelami. Wygląda nieco inaczej:Nullable ForeignKeys i usuwanie odniesienia instancji modelu

class Message(models.Model): 
    sender = models.ForeignKey(User, null=True, blank=True) 
    sender_name = models.CharField(max_length=255) 

Po zapisaniu nazwa nadawcy jest zapisywana w atrybucie nazwa_sektora. Teraz chcę móc usunąć instancję użytkownika przywołaną przez nadawcę i pozostawić wiadomość w miejscu.

Po wyjęciu z pudełka ten kod zawsze powoduje usunięcie wiadomości zaraz po usunięciu instancji użytkownika. Pomyślałem więc, że program obsługi sygnału byłby dobrym pomysłem.

def my_signal_handler(sender, instance, **kwargs): 
    instance.message_set.clear() 
pre_delete.connect(my_signal_handler, sender=User) 

Niestety nie jest to rozwiązanie. W jakiś sposób Django najpierw zbiera to, co chce usunąć, a następnie uruchamia program obsługi pre_delete.

Wszelkie pomysły? Gdzie jest węzeł w moim mózgu?

Odpowiedz

13

Django rzeczywiście emuluje zachowanie SQL ON DELETE CASCADE i nie ma gotowego udokumentowanego sposobu na zmianę tego zachowania. Dokumenty, w których o tym wspominają, znajdują się pod koniec tej sekcji: Deleting objects.

Masz rację, że Django zbiera wszystkie powiązane instancje modelu, a następnie wywołuje procedurę obsługi pre-delete dla każdego z nich. Nadawcą sygnału będzie klasa modelu, która ma zostać usunięta, w tym przypadku Message, a nie User, co utrudnia wykrycie różnicy między kaskadowym kasowaniem wywołanym przez użytkownika a zwykłym kasowaniem ... zwłaszcza, że ​​sygnał usunięcie klasy User jest ostatnim, ponieważ jest to ostatnie usunięcie :-)

Można jednak uzyskać listę obiektów, które Django proponuje usunąć przed wywołaniem funkcji User.delete(). Każda instancja modelu ma półprywatną metodę o nazwie _collect_sub_objects(), która kompiluje listę wystąpień za pomocą obcych kluczy wskazujących na nią (kompiluje tę listę bez usuwania instancji). Możesz zobaczyć, jak wywoływana jest ta metoda, patrząc na delete() w .

Jeśli był to jeden z twoich obiektów, polecam przesłonięcie metody delete() na twojej instancji, aby uruchomić _collect_sub_objects(), a następnie rozbić ForeignKeys przed wywołaniem usuwania klasy super. Ponieważ używasz wbudowanego obiektu Django, który możesz uznać za zbyt trudny do podklasy (chociaż można zastąpić własny obiekt użytkownika dla django), być może będziesz musiał polegać na logice widoku, aby uruchomić _collect_sub_objects i rozbić FK przed usunięcie.

Oto szybki i-brudny przykład:

from django.db.models.query import CollectedObjects 
u = User.objects.get(id=1) 


instances_to_be_deleted = CollectedObjects() 
u._collect_sub_objects(instances_to_be_deleted) 

for k in instances_to_be_deleted.ordered_keys(): 
    inst_dict = instances_to_be_deleted.data[k] 
    for i in inst_dict.values(): 
     i.sender = None # You will need a more generic way for this 
     i.save() 

u.delete() 
+0

Dzięki za wyjaśnienie i wskazówki do kopania. –

+0

Dokładna odpowiedź. –

0

Po prostu odkrył ON DELETE CASCADE zachowanie ruchowe siebie, widzę, że w Django 1.3 zrobili kluczową zachowanie obcego configurable.