2013-03-08 87 views
13

ForeignKey s在django上有屬性on_delete來指定刪除引用對象時的行爲。有什麼辦法可以得到類似ManyToManyField的東西嗎?django ManyToManyField和on_delete

假設我有以下的模型

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

的默認行爲是級聯的,所以如果我刪除發生在自己的房子的人,它只是從業主(即,很明顯,它消失無更長的時間擁有任何房屋)。我想要的是,如果一個人是一個所有者,它不能被刪除。那就是,我想要on_delete=models.PROTECT。這可能嗎?

我在內部知道ManyToManyField被翻譯成另一個模型,其中有兩個ForeignKey(在這種情況下,一個房子和一個人),所以它應該有可能實現這一點。任何想法如何?我想避免將through屬性設置爲新模型,因爲這會導致創建一個新表(我希望保留舊錶)。

編輯:我已經追蹤Django在何處創建相應的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) 
    }) 

相關線上

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

我想它是

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

任何方式來做到這一點,而不是猴子修補整個事情併爲ManyToManyField創建一個新類?

回答

4

我認爲最聰明的做法是使用明確的直通表。我意識到你已經表示你不想「因爲這會導致一個新的表格(我想保留舊的表格)」。

我懷疑你的擔心是失去你所擁有的數據。如果您使用的是South,您可以輕鬆地將現有的自動中間表「轉換」爲明確的OR,您可以創建一個全新的表,然後在刪除舊錶之前將現有數據遷移到新表中。

這些方法都進行了說明:Adding a "through" table to django field and migrating with South?

考慮到你想打它的定義的變化,我可能會與創建新表,那麼你的數據在遷移的選項去。測試以確保您的所有數據仍然存在(並且您的更改按照您的要求進行),然後刪除舊的中間表。

考慮到這些表格每行只能包含3個整數,即使您有很多房屋和業主,這也可能是一個非常易於管理的練習。

+0

感謝您的回答。我不知道我可以如此輕鬆地遷移數據,但是透明的表格會爲我產生太多的膨脹代碼。我有多個ManyToManyField,我不想爲它們中的每一個都有一個明確的表,因此我決定使用猴子修補代碼,並簡單地用一個新類ProtectedManyToManyField替換ManyToManyField – Clash 2013-05-07 00:07:27

+1

@Clash。你好,你是如何設法實現ProtectedManyToManyField的? – 2015-02-04 18:05:57

+0

@AndrewFount - 我不認爲你會得到這個通知,否則,所以我在這個評論中提到你。衝突貼在這裏:http://stackoverflow.com/a/35827978/901641 – ArtOfWarfare 2016-03-07 02:40:25

1

根據@Andrew Fount的要求發佈我自己的解決方案。非常醜陋的黑客只是改變一條線。

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)