ForeignKey
具有属性on_delete,用于指定删除引用对象时的行为。有没有办法为ManyToManyField获得类似的内容?
假设我有以下模型
class House(models.Model):
owners = models.ManyToManyField(Person)
默认行为是级联,所以如果我删除碰巧拥有房子的人,它就会从所有者身上消失(显然,它不再拥有任何房屋)。我想要的是,如果某人是所有者,则无法将其删除。也就是说,我想要on_delete=models.PROTECT
。这可能吗?
我知道内部ManyToManyField
被翻译成另一个模型,其中有两个ForeignKey
s(在这种情况下是一个到一个房子,一个到一个人),所以应该可以实现这一点。任何想法如何?我想避免将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创建一个新类之外,还有什么方法可以做到这一点?
答案 0 :(得分:5)
我认为最明智的做法是使用显式的直通表。我意识到你已经说过你不愿意“因为这会产生一个新表格(我想保留旧表格)。”
我怀疑您关注的是丢失了您的数据。如果您正在使用South,则可以轻松地将现有的自动中间表“转换”为显式的OR,您可以创建一个全新的,然后将现有数据迁移到新表中,然后再删除旧表。
这两种方法都在这里解释:Adding a "through" table to django field and migrating with South?
考虑到您要对其定义所做的更改,我可能会选择创建新表,然后迁移您的数据。测试以确保所有数据仍然存在(并且您的更改符合您的要求),然后删除旧的中间表。
考虑到这些表每行只能容纳3个整数,即使你拥有很多房屋和业主,这也很可能是一个非常易于管理的工作。
答案 1 :(得分: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)
答案 2 :(得分:0)
如果我理解你想要的,这和我前段时间需要的类似。
您的问题:您需要保护另一个表中使用的记录不被意外删除。
我通过这种方式解决了它(在 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)
这是一个丑陋的解决方案,但它有效。