【问题标题】:django ManyToManyField and on_deletedjango ManyToManyField 和 on_delete
【发布时间】:2013-02-24 12:46:49
【问题描述】:

django 上的ForeignKeys 具有on_delete 属性来指定删除引用对象时的行为。有没有办法为ManyToManyField 获得类似的东西?

假设我有以下模型

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

默认行为是级联的,所以如果我删除一个碰巧拥有房子的人,它就会从所有者那里消失(也就是说,很明显,它不再拥有任何房子)。我想要的是,如果一个人是所有者,它就不能被删除。也就是说,我想要on_delete=models.PROTECT。这可能吗?

我知道在内部ManyToManyField 被转换为具有两个ForeignKeys 的另一个模型(在这种情况下,一个用于房屋,一个用于人),因此应该可以实现这一点。任何想法如何?我想避免将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 创建一个新类之外,还有其他方法吗?

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    我认为最明智的做法是使用显式直通表。我知道您已经说过您不希望“因为这会导致一个新表(我想保留旧表)。”

    我怀疑您担心丢失您拥有的数据。如果您使用 South,您可以轻松地将现有的自动中间表“转换”为显式中间表,或者,您可以创建一个全新的表,然后在删除旧表之前将现有数据迁移到新表。

    这里解释了这两种方法:Adding a "through" table to django field and migrating with South?

    考虑到您希望对其定义进行更改,我可能会选择创建一个新表,然后迁移您的数据。测试以确保您的所有数据仍然存在(并且您的更改符合您的要求),然后删除旧的中间表。

    考虑到这些表每行都只能保存 3 个整数,即使您有很多房屋和所有者,这也可能是一个非常易于管理的练习。

    【讨论】:

    • 感谢您的回答。我不知道我可以如此轻松地迁移数据,但是明确的直通表会为我生成过多的臃肿代码。我有多个 ManyToManyField,我不想为它们中的每一个都有一个明确的表,因此我决定用猴子修补代码并简单地将 ManyToManyField 替换为新类 ProtectedManyToManyField
    • @Clash。您好,您是如何实现 ProtectedManyToManyField 的?
    • @AndrewFount - 我不认为你会收到通知,所以我在这条评论中提到了你。冲突在这里发布:stackoverflow.com/a/35827978/901641
    【解决方案2】:

    如果我明白你想要的话,这与我前段时间需要的类似。

    您的问题:您需要保护在另一个表中使用的记录不被意外删除。

    我通过这种方式解决了它(在 Django 2 和 Django 3 上测试过)。

    想象一下,你有:

    TABLE1 和 TABLE 2,它们属于 M2M 关系,其中 TABLE1 具有 ManyToManyField。

    我把你理解的主键大写,你需要调整到你想要的。

    查看使用exists()方法的views.py并引发异常是至关重要的。

    models.py

    class TABLE1(models.Model):
        FIELD_M2M = models.ManyToManyField(
            TABLE2,
            blank=False,
            related_name='FIELD_M2M',
        )
    #put here your code
    

    models.py

    class TABLE2(models.Model):
    #Put here your code
    

    views.py

    # Delete
    @login_required
    def delete(request, pk=None):
        try:  # Delete register selected
            if TABLE1.objects.filter(FIELD_M2M=pk).exists():
                raise IntegrityError
            register_to_delete = get_object_or_404(TABLE2, pk=pk)
            # register_to_delete.register_to_delete.clear() // Uncomment this, if you need broken relationship M2M before delete
            register_to_delete.delete()
        except IntegrityError:
            message = "The register couldn't be deleted!"
            messages.info(request, message)
    

    这是一个丑陋的解决方案,但它有效。

    【讨论】:

      【解决方案3】:

      按照@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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-27
        • 2018-11-03
        • 2020-06-14
        • 2016-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-22
        相关资源
        最近更新 更多