如何用Django软删除多对多的关系

时间:2012-02-21 09:22:38

标签: python django django-models

在我的Django项目中,必须通过将当前日期时间设置为deleted_at属性来软删除用户删除的所有实体。我的模型如下所示:Trip< - > TripDestination< - >目的地(多对多关系)。换句话说,Trip可以有多个目的地。

当我删除Trip时,SoftDeleteManager会过滤掉所有已删除的行程。但是,如果我请求旅行的所有目的地(使用get_object_or_404(Trip,pk = id)),我也会得到删除的目的地(即具有deleted_at == null或者deleted_at!= null的TripDestination模型)。我真的不明白为什么我的所有模型都继承自LifeTimeTracking并使用SoftDeleteManager。

有人可以帮我理解为什么SoftDeleteManager不能用于n:m关系吗?

class SoftDeleteManager(models.Manager):
    def get_query_set(self):
        query_set = super(SoftDeleteManager, self).get_query_set()
        return query_set.filter(deleted_at__isnull = True)

class LifeTimeTrackingModel(models.Model):
    created_at = models.DateTimeField(auto_now_add = True)
    updated_at = models.DateTimeField(auto_now = True)
    deleted_at = models.DateTimeField(null = True)

    objects = SoftDeleteManager()
    all_objects = models.Manager()

    class Meta:
        abstract = True

class Destination(LifeTimeTrackingModel):
    city_name = models.CharField(max_length = 45)

class Trip(LifeTimeTrackingModel):
    name = models.CharField(max_length = 250)
    destinations = models.ManyToManyField(Destination, through = 'TripDestination')

class TripDestination(LifeTimeTrackingModel):
    trip = models.ForeignKey(Trip)
    destination = models.ForeignKey(Destination)

解决 我在Django Bug DB中提交了错误17746。感谢Caspar对此的帮助。

2 个答案:

答案 0 :(得分:2)

看起来这种行为来自于ManyToManyField选择使用自己的管理器,Related objects reference提到了,因为当我尝试编写一些我自己的实例时。尝试使用您的模型代码软件删除它们(通过manage.py shell)一切都按预期工作。

不幸的是,它没有提到如何覆盖模型管理器。我花了大约15分钟搜索了ManyToManyField源代码,但没有跟踪它实例化其管理器的位置(查看django / db / models / fields / related.py)。

要获得您所追求的行为,您应在use_for_related_fields = True上指定SoftDeleteManagercontrolling automatic managers上的文档指定:

class SoftDeleteManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        query_set = super(SoftDeleteManager, self).get_query_set()
        return query_set.filter(deleted_at__isnull = True)

这可以按预期工作:我可以通过Trip定义DestinationTripDestinationDestination,如果我设置了deleted_at datetime.datetime.now()值为Destination,然后mytrip.destinations.all()不再显示在{{1}}给出的列表中,这就是我所知道的那样。

但是,文档还专门说do not filter the query set by overriding get_query_set() on a manager used for related fields,所以如果您以后遇到问题,请记住这可能是原因。

答案 1 :(得分:2)

要按deleted_at的{​​{1}}字段启用过滤,Destinantion模型设置Tripuse_for_related_fields = True就足够了。根据Caspar的回答,这不会为SoftDeleteManager返回已删除的Destinations

但是,根据您的评论,我们可以看到您希望过滤掉trip_object.destinations.all()Destinations相关联的Trip来自TripDestination对象,其中包含deleted_at字段。在实例上删除。

让我们澄清管理者的工作方式。相关经理是远程模型的管理者,而不是直通模型。

trip_object.destinantions.some_method()调用默认Destination经理。 destinantion_object.trip_set.some_method()调用默认Trip经理。 TripDestination经理在任何时候都不会被召唤。

如果您愿意,可以使用trip_object.destinantions.through.objects.some_method()进行调用。现在,我要做的是添加一个实例方法Trip.get_destinations和一个过滤掉已删除连接的类似Destination.get_trips

如果您坚持使用管理器进行过滤,则会变得更加复杂:

class DestinationManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        query_set = super(DestinationManager, self).get_query_set()
        if hasattr(self, "through"):
            through_objects = self.through.objects.filter(
                destination_id=query_set.filter(**self.core_filters).get().id,
                trip_id=self._fk_val,
                deleted_at__isnull=True)
            query_set = query_set.filter(
                id__in=through_objects.values("destination_id"))

        return query_set.filter(deleted_at__isnull = True)

TripManager必须做同样的事情,因为它们会有所不同。您可以查看效果并查看django/db/models/fields/related.py以供参考。

修改默认管理器的get_queryset方法可能会妨碍备份数据库的能力,文档也不鼓励这样做。编写Trip.get_destinations方法是另一种选择。