2013-03-08 14 views
13

ForeignKey s na django mają atrybut on_delete, aby określić zachowanie, gdy usuwany obiekt jest usuwany. Czy istnieje sposób, aby uzyskać coś podobnego dla ManyToManyField?django ManyToManyField i on_delete

Załóżmy, że mam następujący model

class House(models.Model): 
    owners = models.ManyToManyField(Person) 

Domyślnym zachowaniem jest kaskadowo, więc jeśli mogę usunąć osobę, która dzieje się właścicielem domu, to po prostu znika z właścicieli (to jest, oczywiście, to nie dłużej posiada wszystkie domy). Chciałbym mieć to, że jeśli osoba jest właścicielem, nie można jej usunąć. To znaczy, chcę on_delete=models.PROTECT. czy to możliwe?

Znam wewnętrznie ManyToManyField został przetłumaczony na inny model z dwoma ForeignKey s (w tym przypadku jeden do domu i jeden do osoby), więc powinno być możliwe do osiągnięcia tego. Wszelkie pomysły, jak? Chciałbym uniknąć ustawienia atrybutu through na nowy model, ponieważ spowodowałoby to utworzenie nowej tabeli (chciałbym zachować poprzednią).

Edit: Mam śledzone gdzie Django tworzy odpowiedni model m2m:

def create_many_to_many_intermediary_model(field, klass): 
    from django.db import models 
    # ... 
    # Construct and return the new class. 
    return type(name, (models.Model,), { 
     'Meta': meta, 
     '__module__': klass.__module__, 
     from_: models.ForeignKey(klass, 
           related_name='%s+' % name, 
           db_tablespace=field.db_tablespace), 
     to: models.ForeignKey(to_model, 
           related_name='%s+' % name, 
           db_tablespace=field.db_tablespace) 
    }) 

Odpowiednia linia jest

to: models.ForeignKey(to_model, 
         related_name='%s+' % name, 
         db_tablespace=field.db_tablespace) 

Chciałbym go mieć

to: models.ForeignKey(to_model, 
         related_name='%s+' % name, 
         db_tablespace=field.db_tablespace, 
         on_delete=models.PROTECT) 

Jakikolwiek sposób to zrobić, oprócz małpowania łatania całej rzeczy i tworzenia nowej klasy dla ManyToManyField?

Odpowiedz

4

Myślę, że najmądrzejszą rzeczą do zrobienia jest użycie wyraźnej tabeli. Zdaję sobie sprawę, że powiedziałeś, że wolałbyś nie ", ponieważ spowodowałoby to nowy stół (chciałbym zachować stary)".

Podejrzewam, że twoje obawy mijają z utratą danych, które posiadasz. Jeśli korzystasz z South, możesz łatwo "przekonwertować" istniejącą, automatyczną tabelę pośrednią na jawną LUB, możesz utworzyć zupełnie nową, a następnie przenieść swoje istniejące dane do nowej tabeli przed upuszczeniem starej.

Obie te metody są opisane tutaj: Adding a "through" table to django field and migrating with South?

Biorąc pod uwagę zmiany, które chcesz wprowadzić do jego definicji, pewnie bym go z opcją tworzenia nowej tabeli, a następnie migracji danych skończona. Przetestuj, aby upewnić się, że wszystkie twoje dane nadal tam są (i że twoja zmiana robi to, czego chcesz), a następnie upuść starą tabelę pośrednią.

Biorąc pod uwagę, że te tabele będą zawierać tylko 3 liczby całkowite w wierszu, może to być bardzo łatwe do opanowania, nawet jeśli masz dużo domów i właścicieli.

+0

dzięki za odpowiedź. Nie wiedziałem, że mogę tak łatwo migrować dane, ale wyraźna tabela wygenerowałaby dla mnie za dużo kodu. Mam wiele ManyToManyField i nie chcę mieć wyraźnej tabeli dla każdego z nich i dlatego zdecydowałem się pójść z małpią łatając kod i po prostu zastępując ManyToManyField nową klasą ProtectedManyToManyField – Clash

+1

@Clash.Cześć, jak zdołałeś wdrożyć ProtectedManyToManyField? –

+0

@AndrewFount - Nie sądzę, że otrzymasz powiadomienie o tym inaczej, więc wspominam o Tobie w tym komentarzu. Clash opublikował go tutaj: http://stackoverflow.com/a/35827978/901641 – ArtOfWarfare

1

Zamieszczam własne rozwiązanie na żądanie @Andrew Fount. Całkiem brzydki hack, żeby zmienić jedną linię.

from django.db.models import ManyToManyField 
from django.db.models.fields.related import ReverseManyRelatedObjectsDescriptor, add_lazy_relation, create_many_to_many_intermediary_model, RECURSIVE_RELATIONSHIP_CONSTANT 
from django.utils import six 
from django.utils.functional import curry 


def create_many_to_many_protected_intermediary_model(field, klass): 
    from django.db import models 
    managed = True 
    if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: 
     to_model = field.rel.to 
     to = to_model.split('.')[-1] 

     def set_managed(field, model, cls): 
      field.rel.through._meta.managed = model._meta.managed or cls._meta.managed 
     add_lazy_relation(klass, field, to_model, set_managed) 
    elif isinstance(field.rel.to, six.string_types): 
     to = klass._meta.object_name 
     to_model = klass 
     managed = klass._meta.managed 
    else: 
     to = field.rel.to._meta.object_name 
     to_model = field.rel.to 
     managed = klass._meta.managed or to_model._meta.managed 
    name = '%s_%s' % (klass._meta.object_name, field.name) 
    if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: 
     from_ = 'from_%s' % to.lower() 
     to = 'to_%s' % to.lower() 
    else: 
     from_ = klass._meta.object_name.lower() 
     to = to.lower() 
    meta = type('Meta', (object,), { 
     'db_table': field._get_m2m_db_table(klass._meta), 
     'managed': managed, 
     'auto_created': klass, 
     'app_label': klass._meta.app_label, 
     'db_tablespace': klass._meta.db_tablespace, 
     'unique_together': (from_, to), 
     'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to}, 
     'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to}, 
     }) 
    # Construct and return the new class. 
    return type(name, (models.Model,), { 
     'Meta': meta, 
     '__module__': klass.__module__, 
     from_: models.ForeignKey(klass, related_name='%s+' % name, db_tablespace=field.db_tablespace), 

     ### THIS IS THE ONLY LINE CHANGED 
     to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, on_delete=models.PROTECT) 
     ### END OF THIS IS THE ONLY LINE CHANGED 
    }) 


class ManyToManyProtectedField(ManyToManyField): 
    def contribute_to_class(self, cls, name): 
     # To support multiple relations to self, it's useful to have a non-None 
     # related name on symmetrical relations for internal reasons. The 
     # concept doesn't make a lot of sense externally ("you want me to 
     # specify *what* on my non-reversible relation?!"), so we set it up 
     # automatically. The funky name reduces the chance of an accidental 
     # clash. 
     if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): 
      self.rel.related_name = "%s_rel_+" % name 

     super(ManyToManyField, self).contribute_to_class(cls, name) 

     # The intermediate m2m model is not auto created if: 
     # 1) There is a manually specified intermediate, or 
     # 2) The class owning the m2m field is abstract. 
     # 3) The class owning the m2m field has been swapped out. 
     if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: 
      self.rel.through = create_many_to_many_protected_intermediary_model(self, cls) 

     # Add the descriptor for the m2m relation 
     setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 

     # Set up the accessor for the m2m table name for the relation 
     self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 

     # Populate some necessary rel arguments so that cross-app relations 
     # work correctly. 
     if isinstance(self.rel.through, six.string_types): 
      def resolve_through_model(field, model, cls): 
       field.rel.through = model 
      add_lazy_relation(cls, self, self.rel.through, resolve_through_model) 
Powiązane problemy