2012-02-21 93 views
5

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

当我删除一个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) 

分辨率 我提交的bug 17746在Django错误DB。感谢卡斯帕在这方面的帮助。

回答

2

它看起来像这样的行为来自ManyToManyField选择使用自己的经理,该Related objects reference提到,因为当我尝试做了一些我自己的实例&尝试软删除它们使用模型的代码(通过管理.py shell)一切都按预期工作。

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

为了让你以后的行为,你应该在你的SoftDeleteManager类由文档上controlling automatic managers规定指定use_for_related_fields = True

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与2个Destination S,每经过一个TripDestination,如果我一个Destinationdeleted_at值设置为datetime.datetime.now()那么Destination不再出现在由mytrip.destinations.all()给出的列表中,这就是你接近我可以告诉以后。

但是,文档还具体说do not filter the query set by overriding get_query_set() on a manager used for related fields,所以如果您稍后遇到问题,请牢记这一点作为可能的原因。

+0

@Martin我已经更新了我的答案,请看看它是否适合你:) – Caspar 2012-02-22 01:44:44

+0

你好卡斯帕!谢谢你的协助。但是我正在寻找的是如果Tripdesrimation已被删除,而不是目的地,则看不到目的地。 – Martin 2012-02-22 02:37:55

+0

啊,我明白你的意思了。我试过了,它不能与我的解决方案一起工作,我认为这个解决方案指出了一个错误。通过bug数据库的[快速搜索](https://code.djangoproject.com/search?q=use_for_related_fields)并不能解决任何问题(尽管存在一个[几乎相反的问题](https:// code.djangoproject.com/ticket/14891)),那么可能是时候提出bug了。 – Caspar 2012-02-22 07:09:24

2

要启用筛选deleted_at字段DestinantionTrip模型设置use_for_related_fields = True对于SoftDeleteManager类是足够的。根据卡斯帕的回答,这不会返回删除Destinations

但是从您的意见,我们可以看到你想过滤出Destinations,它们通过TripDestination对象链接到Trip一组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方法是另一种方法。