在我的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对此的帮助。
答案 0 :(得分:2)
看起来这种行为来自于ManyToManyField选择使用自己的管理器,Related objects reference提到了,因为当我尝试编写一些我自己的实例时。尝试使用您的模型代码软件删除它们(通过manage.py shell)一切都按预期工作。
不幸的是,它没有提到如何覆盖模型管理器。我花了大约15分钟搜索了ManyToManyField源代码,但没有跟踪它实例化其管理器的位置(查看django / db / models / fields / related.py)。
要获得您所追求的行为,您应在use_for_related_fields = True
上指定SoftDeleteManager
,controlling 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
定义Destination
个TripDestination
个Destination
,如果我设置了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
模型设置Trip
类use_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
方法是另一种选择。