我正在将我的项目从Django 2.2升级到3.2,并绞尽脑汁解决他们代码中似乎存在的bug。
我有一个测试,它对一个资源(顺便说一下,是一个DjangoRestFramework资源,DRF版本是3.12.4)执行一个简单的DELETE请求,在django.db.models.deletion内部发生了崩溃。下面是堆栈跟踪的相关部分:
response = admin_client.delete(
reverse(self.url_name, args=[project.pk, category.pk]),
> content_type='application/json'
)
test_category_views.py:609:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/home/rn/venvs/lib/python3.6/site-packages/django/test/client.py:795: in delete
response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra)
/home/rn/venvs/lib/python3.6/site-packages/django/test/client.py:447: in delete
secure=secure, **extra)
/home/rn/venvs/lib/python3.6/site-packages/django/test/client.py:473: in generic
return self.request(**r)
/home/rn/venvs/lib/python3.6/site-packages/django/test/client.py:719: in request
self.check_exception(response)
/home/rn/venvs/lib/python3.6/site-packages/django/test/client.py:580: in check_exception
raise exc_value
/home/rn/venvs/lib/python3.6/site-packages/django/core/handlers/exception.py:47: in inner
response = get_response(request)
../../../hazardlog/platform.py:127: in _get_response
response = self.process_exception_by_middleware(e, request)
../../../hazardlog/platform.py:125: in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
/usr/local/lib/python3.6/contextlib.py:52: in inner
return func(*args, **kwds)
/home/rn/venvs/lib/python3.6/site-packages/django/views/decorators/csrf.py:54: in wrapped_view
return view_func(*args, **kwargs)
/home/rn/venvs/lib/python3.6/site-packages/django/views/generic/base.py:70: in view
return self.dispatch(request, *args, **kwargs)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/views.py:509: in dispatch
response = self.handle_exception(exc)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/views.py:469: in handle_exception
self.raise_uncaught_exception(exc)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/views.py:480: in raise_uncaught_exception
raise exc
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/views.py:506: in dispatch
response = handler(request, *args, **kwargs)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/generics.py:291: in delete
return self.destroy(request, *args, **kwargs)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/mixins.py:91: in destroy
self.perform_destroy(instance)
/home/rn/venvs/lib/python3.6/site-packages/rest_framework/mixins.py:95: in perform_destroy
instance.delete()
/home/rn/venvs/lib/python3.6/site-packages/django/db/models/base.py:953: in delete
collector.collect([self], keep_parents=keep_parents)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <django.db.models.deletion.Collector object at 0x7f7870265ac8>
objs = [<Category: Test category 0>], source = None, nullable = False
collect_related = True, source_attr = None, reverse_dependency = False
keep_parents = False, fail_on_restricted = True
def collect(self, objs, source=None, nullable=False, collect_related=True,
source_attr=None, reverse_dependency=False, keep_parents=False,
fail_on_restricted=True):
"""
Add 'objs' to the collection of objects to be deleted as well as all
parent instances. 'objs' must be a homogeneous iterable collection of
model instances (e.g. a QuerySet). If 'collect_related' is True,
related objects will be handled by their respective on_delete handler.
If the call is the result of a cascade, 'source' should be the model
that caused it and 'nullable' should be set to True, if the relation
can be null.
If 'reverse_dependency' is True, 'source' will be deleted before the
current model, rather than after. (Needed for cascading to parent
models, the one case in which the cascade follows the forwards
direction of an FK rather than the reverse direction.)
If 'keep_parents' is True, data of parent model's will be not deleted.
If 'fail_on_restricted' is False, error won't be raised even if it's
prohibited to delete such objects due to RESTRICT, that defers
restricted object checking in recursive calls where the top-level call
may need to collect more objects to determine whether restricted ones
can be deleted.
"""
if self.can_fast_delete(objs):
self.fast_deletes.append(objs)
return
new_objs = self.add(objs, source, nullable,
reverse_dependency=reverse_dependency)
if not new_objs:
return
model = new_objs[0].__class__
if not keep_parents:
# Recursively collect concrete model's parent models, but not their
# related objects. These will be found by meta.get_fields()
concrete_model = model._meta.concrete_model
for ptr in concrete_model._meta.parents.values():
if ptr:
parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
self.collect(parent_objs, source=model,
source_attr=ptr.remote_field.related_name,
collect_related=False,
reverse_dependency=True,
fail_on_restricted=False)
if not collect_related:
return
if keep_parents:
parents = set(model._meta.get_parent_list())
model_fast_deletes = defaultdict(list)
protected_objects = defaultdict(list)
for related in get_candidate_relations_to_delete(model._meta):
# Preserve parent reverse relationships if keep_parents=True.
if keep_parents and related.model in parents:
continue
field = related.field
if field.remote_field.on_delete == DO_NOTHING:
continue
related_model = related.related_model
if self.can_fast_delete(related_model, from_field=field):
model_fast_deletes[related_model].append(field)
continue
batches = self.get_del_batches(new_objs, [field])
for batch in batches:
> sub_objs = self.related_objects(related_model, [field], batch)
E TypeError: related_objects() takes 3 positional arguments but 4 were given
/home/rn/venvs/ramrisk/lib/python3.6/site-packages/django/db/models/deletion.py:282: TypeError因此,related_objects使用4个位置参数调用,尽管它应该只接受3。Python使用这些参数计算self,所以4是正确的。它被self、related_model、[field]和batch调用。太棒了。那么,让我们看看django.db.models.deletion中self.related_objects的定义:
def related_objects(self, related_model, related_fields, objs):
"""
Get a QuerySet of the related model to objs via related fields.
"""
predicate = reduce(operator.or_, (
query_utils.Q(**{'%s__in' % related_field.name: objs})
for related_field in related_fields
))
return related_model._base_manager.using(self.using).filter(predicate)好吧,很明显,这需要4个参数,那么这个TypeError是从哪里来的呢?请记住,我只展示了Django 3.2代码,没有我自己的代码。即使我以某种方式在这些变量中加入了一些疯狂的东西,它们也永远不会产生那个错误……
发布于 2021-08-05 06:09:14
好了,找到我的答案了。实际上,这可能是没有人能够猜到的事情,但我只想分享我学到的东西。
所以我是对的,这个错误没有意义,因为它不符合函数签名。这应该永远不会发生。那么如何调试呢?
嗯,我的第一个直觉是,也许这个函数定义在运行时的某个地方被替换了。我该怎么检查呢?我在调用该方法的行上设置了一个断点,然后在我的调试器中
import inspect
inspect.getmodule(self.related_objects)这给了我答案。看起来我们已经修补了这个函数,用额外的功能来装饰它,但是现在Django版本升级了,预期的签名也改变了。
这就是为什么Monkey补丁是危险的,不能随意使用。这里的教训:如果你做的是猴子补丁,那么每次升级lib版本时,一定要重新访问所有这些补丁。这一次失败了,所以我必须找出发生了什么,但它很容易就会变得更加险恶。它可能会默默地做一些不同和不正确的事情,因为库有一些小的变化,而猴子补丁并没有意识到这一点。
https://stackoverflow.com/questions/68660787
复制相似问题