2013-03-08 33 views
13

ForeignKey s trên django có thuộc tính on_delete để chỉ định hành vi khi đối tượng được tham chiếu bị xóa. Có cách nào để có được một cái gì đó tương tự cho ManyToManyField?django ManyToManyField và on_delete

Giả sử tôi có mô hình sau

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

Hành vi mặc định là thác, vì vậy nếu tôi xóa một người điều đó xảy ra để sở hữu một ngôi nhà, nó chỉ biến mất từ ​​chủ sở hữu (có nghĩa là, rõ ràng, nó không còn sở hữu bất kỳ nhà nào). Những gì tôi muốn có là nếu một người là chủ sở hữu, nó không thể bị xóa. Đó là, tôi muốn on_delete=models.PROTECT. Điều này có thể không?

Tôi biết nội bộ ManyToManyField được dịch sang một mô hình khác với hai ForeignKey s (trong trường hợp này là một nhà và một người), do đó, có thể đạt được điều này. Bất kỳ ý tưởng làm thế nào để? Tôi muốn tránh đặt thuộc tính through thành mô hình mới, vì điều này sẽ dẫn đến một bảng mới (tôi muốn giữ lại bảng cũ).

Edit: Tôi đã theo dõi nơi django tạo ra mô hình m2m thích hợp:

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

Dòng có liên quan là

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

Tôi muốn nó được

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

Bất kỳ cách nào để làm điều này khác hơn là khỉ vá toàn bộ điều và tạo ra một lớp học mới cho ManyToManyField?

Trả lời

4

Tôi nghĩ điều thông minh nhất cần làm là sử dụng bảng rõ ràng thông qua bảng. Tôi nhận ra rằng bạn đã tuyên bố rằng bạn không muốn "bởi vì điều này sẽ dẫn đến một bảng mới (tôi muốn giữ cái cũ)."

Tôi nghi ngờ mối quan tâm của bạn là mất dữ liệu bạn có. Nếu bạn đang sử dụng miền Nam, bạn có thể dễ dàng "chuyển đổi" bảng trung gian tự động hiện tại thành bảng OR rõ ràng, bạn có thể tạo bảng mới hoàn toàn, sau đó di chuyển dữ liệu hiện có của bạn sang bảng mới trước khi xóa bảng cũ.

Cả hai phương pháp được giải thích ở đây: Adding a "through" table to django field and migrating with South?

Xét sự thay đổi mà bạn muốn thực hiện để định nghĩa của nó, tôi có thể đi với các tùy chọn của việc tạo ra một bảng mới, sau đó di chuyển dữ liệu của bạn kết thúc. Kiểm tra để đảm bảo rằng tất cả dữ liệu của bạn vẫn còn đó (và thay đổi của bạn làm những gì bạn muốn), sau đó thả bảng trung gian cũ.

Xem xét các bảng này sẽ chỉ giữ 3 số nguyên mỗi hàng, đây có thể là một bài tập rất dễ quản lý ngay cả khi bạn có nhiều nhà và chủ sở hữu.

+0

cảm ơn câu trả lời. Tôi không biết tôi có thể di chuyển dữ liệu dễ dàng như vậy, nhưng một bảng thông qua bảng sẽ tạo ra quá nhiều mã bloat cho tôi. Tôi có nhiều ManyToManyField và tôi không muốn có một bảng rõ ràng cho mỗi một trong số họ và do đó tôi quyết định đi với khỉ vá mã và chỉ cần thay thế ManyToManyField với một lớp mới ProtectedManyToManyField – Clash

+1

@Clash.Xin chào, làm thế nào bạn quản lý để thực hiện ProtectedManyToManyField? –

+0

@AndrewFount - Tôi không nghĩ rằng bạn sẽ nhận được thông báo về điều này nếu không, vì vậy tôi đang đề cập đến bạn trong nhận xét này. Clash đăng nó ở đây: http://stackoverflow.com/a/35827978/901641 – ArtOfWarfare

1

Đăng giải pháp của riêng tôi theo yêu cầu của @Andrew Fount. Khá một hack xấu xí chỉ để thay đổi một dòng.

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) 
Các vấn đề liên quan