Django Rest Framework权限测试问题:如何刷新关联关系?
解决DRF权限类测试中反向关联QuerySet缓存导致的校验失败问题
看起来你遇到的问题是Django反向关联QuerySet的缓存机制在搞鬼!让我一步步帮你理清:
问题根源
当你调用self.restaurant.refresh_from_db()时,这个方法只会刷新Restaurant对象自身的字段(比如name、slug这些),不会刷新它的反向关联(比如table_set)。如果在测试中你之前访问过self.restaurant.table_set(哪怕只是打印它),Django会缓存这个QuerySet的结果,后续即使数据库里已经有了关联的Table记录,缓存的空结果也会被复用,导致restaurant.table_set.filter(...).exists()返回False。
而你看到table.restaurant == self.restaurant为True,是因为Table对象的restaurant外键字段是直接存在于Table表中的,它的关联是实时的,不受反向QuerySet缓存的影响。
解决方案1:优化权限类的校验逻辑(推荐)
其实你的权限校验完全不需要通过restaurant.table_set来查询,直接对比餐桌的餐厅和请求中指定的餐厅是否一致即可,这样不仅避免了缓存问题,还更高效:
from annoying.functions import get_object_or_None from django.utils.translation import ugettext_lazy as _ from rest_framework import permissions from restaurants.models import Restaurant class TableBelongsToRestaurantPermission(permissions.BasePermission): """ Permission to check if the table belongs to the restaurant in the request. This ensures (together with the UserOwnsRestaurantPermission) that owner can change the QR code of a restaurant that he doesn't own. """ message = TABLE_BELONGS_TO_RESTAURANT_PERMISSION_DENIED_MESSAGE def has_object_permission(self, request, view, obj): if not obj.table: return True slug = request.data.get("restaurant_slug", "") restaurant = get_object_or_None(Restaurant, slug=slug) if restaurant: # 直接对比餐桌的餐厅和请求中的餐厅,跳过反向QuerySet查询 return obj.table.restaurant == restaurant return False
解决方案2:在测试中刷新反向关联的QuerySet
如果你一定要保留原来的权限逻辑,那在测试中需要强制刷新table_set的缓存,而不是只刷新Restaurant对象。可以通过以下方式:
# 在测试方法中,创建Table并关联后,不要只调用refresh_from_db() # 重新从数据库获取Restaurant对象,彻底绕过缓存 self.restaurant = Restaurant.objects.get(pk=self.restaurant.pk)
这样重新查询出来的Restaurant对象,它的table_set会重新从数据库加载数据,不会使用之前的缓存。
为什么推荐方案1?
- 性能更好:不需要额外执行一次
table_set.filter的数据库查询,直接对比外键对象即可 - 避免缓存陷阱:完全绕开了Django反向关联QuerySet的缓存问题,后续不会再遇到类似的测试或生产环境问题
- 逻辑更直观:校验逻辑直接表达了“餐桌的餐厅是否等于请求中的餐厅”,比通过反向查询存在性更清晰
内容的提问来源于stack exchange,提问作者J. Hesters
相关产品推荐
相关产品推荐

