You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级Django 5.0后Mezzanine报TypeError: QuerySet._clone()参数异常

Django 5.0 升级后 Mezzanine 出现 TypeError: QuerySet._clone() got an unexpected argument '_search_terms' 修复方案

问题背景

通过NetBSD软件包安装Mezzanine(NetBSD环境下最简方式),升级至Django 5.0后触发错误:TypeError: QuerySet._clone() got an unexpected argument '_search_terms'。已解决其他模块升级适配问题,经分析需修改mezzanine/core/managers.py中的SearchableQuerySet类修复该搜索参数兼容问题。

报错堆栈

Traceback (most recent call last):
 File "/home/nate/cleanair/manage.py", line 14, in <module>
   execute_from_command_line(sys.argv)
   File "/usr/pkg/lib/python3.11/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
     utility.execute()
   File "/usr/pkg/lib/python3.11/site-packages/django/core/management/__init__.py", line 395, in execute
     django.setup()
   File "/usr/pkg/lib/python3.11/site-packages/django/__init__.py", line 24, in setup
     apps.populate(settings.INSTALLED_APPS)
   File "/usr/pkg/lib/python3.11/site-packages/django/apps/registry.py", line 122, in populate
     app_config.ready()
   File "/usr/pkg/lib/python3.11/site-packages/django/contrib/admin/apps.py", line 27, in ready
     self.module.autodiscover()
   File "/usr/pkg/lib/python3.11/site-packages/mezzanine/boot/__init__.py", line 114, in autodiscover
     django_autodiscover(*args, **kwargs)
   File "/usr/pkg/lib/python3.11/site-packages/django/contrib/admin/__init__.py", line 24, in autodiscover
     autodiscover_modules('admin', register_to=site)
   File "/usr/pkg/lib/python3.11/site-packages/django/utils/module_loading.py", line 47, in autodiscover_modules
     import_module('%s.%s' % (app_config.name, module_to_search))
   File "/usr/pkg/lib/python3.11/importlib/__init__.py", line 126, in import_module
     return _bootstrap._gcd_import(name[level:], package, level)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
   File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
   File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
   File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
   File "<frozen importlib._bootstrap_external>", line 940, in exec_module
   File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
   File "/usr/pkg/lib/python3.11/site-packages/mezzanine/forms/admin.py", line 43, in <module>
     class FieldAdminInlineForm(DynamicInlineAdminForm):
   File "/usr/pkg/lib/python3.11/site-packages/django/forms/models.py", line 261, in __new__
     fields = fields_for_model(
              ^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/forms/models.py", line 183, in fields_for_model
     formfield = f.formfield(**kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/db/models/fields/related.py", line 991, in formfield
     'queryset': self.remote_field.model._default_manager.using(using),
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/db/models/manager.py", line 85, in manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/mezzanine/core/managers.py", line 386, in get_queryset
     return super(DjangoCSM, self).get_queryset().filter(**lookup)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/db/models/query.py", line 941, in filter
     return self._filter_or_exclude(False, args, kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/db/models/query.py", line 956, in _filter_or_exclude
     clone = self._chain()
             ^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/django/db/models/query.py", line 1301, in _chain
     obj = self._clone()
           ^^^^^^^^^^^^^
   File "/usr/pkg/lib/python3.11/site-packages/mezzanine/core/managers.py", line 180, in _clone
     return super(SearchableQuerySet, self)._clone(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: QuerySet._clone() got an unexpected argument '_search_terms'

相关源码(mezzanine/core/managers.py 中 SearchableQuerySet 类)

class SearchableQuerySet(QuerySet):
"""
QuerySet providing main search functionality for
``SearchableManager``.
"""

def __init__(self, *args, **kwargs):
    self._search_ordered = False
    self._search_terms = set()
    self._search_fields = kwargs.pop("search_fields", {})
    super(SearchableQuerySet, self).__init__(*args, **kwargs)

def search(self, query, search_fields=None):
    """
    Build a queryset matching words in the given search query,
    treating quoted terms as exact phrases and taking into
    account + and - symbols as modifiers controlling which terms
    to require and exclude.
    """

    # ### DETERMINE FIELDS TO SEARCH ###

    # Use search_fields arg if given, otherwise use search_fields
    # initially configured by the manager class.
    if search_fields:
        self._search_fields = search_fields_to_dict(search_fields)
    if not self._search_fields:
        return self.none()

    # ### BUILD LIST OF TERMS TO SEARCH FOR ###

    # Remove extra spaces, put modifiers inside quoted terms.
    terms = " ".join(query.split()).replace("+ ", "+")     \
                                   .replace('+"', '"+')    \
                                   .replace("- ", "-")     \
                                   .replace('-"', '"-')    \
                                   .split('"')
    # Strip punctuation other than modifiers from terms and create
    # terms list, first from quoted terms and then remaining words.
    terms = [ ("" if t[0:1] not in "+-" else t[0:1]) + t.strip(punctuation)
        for t in terms[1::2] + "".join(terms[::2]).split() ]
    # Remove stop words from terms that aren't quoted or use
    # modifiers, since words with these are an explicit part of
    # the search query. If doing so ends up with an empty term
    # list, then keep the stop words.
    terms_no_stopwords = [t for t in terms if t.lower() not in
        settings.STOP_WORDS]
    get_positive_terms = lambda terms: [t.lower().strip(punctuation)
        for t in terms if t[0:1] != "-"]
    positive_terms = get_positive_terms(terms_no_stopwords)
    if positive_terms:
        terms = terms_no_stopwords
    else:
        positive_terms = get_positive_terms(terms)
    # Append positive terms (those without the negative modifier)
    # to the internal list for sorting when results are iterated.
    if not positive_terms:
        return self.none()
    else:
        self._search_terms.update(positive_terms)

    # ### BUILD QUERYSET FILTER ###

    # Create the queryset combining each set of terms.
    excluded = [reduce(iand, [~Q(**{"%s__icontains" % f: t[1:]}) for f in
        self._search_fields.keys()]) for t in terms if t[0:1] == "-"]
    required = [reduce(ior, [Q(**{"%s__icontains" % f: t[1:]}) for f in
        self._search_fields.keys()]) for t in terms if t[0:1] == "+"]
    optional = [reduce(ior, [Q(**{"%s__icontains" % f: t}) for f in
        self._search_fields.keys()]) for t in terms if t[0:1] not in "+-"]
    queryset = self
    if excluded:
        queryset = queryset.filter(reduce(iand, excluded))
    if required:
        queryset = queryset.filter(reduce(iand, required))
    # Optional terms aren't relevant to the filter if there are
    # terms that are explicitly required.
    elif optional:
        queryset = queryset.filter(reduce(ior, optional))
    return queryset.distinct()

def _clone(self, *args, **kwargs):
    """
    Ensure attributes are copied to subsequent queries.
    """
    for attr in ("_search_terms", "_search_fields", "_search_ordered"):
        kwargs[attr] = getattr(self, attr)
    return super(SearchableQuerySet, self)._clone(*args, **kwargs)

def order_by(self, *field_names):
    """
    Mark the filter as being ordered if search has occurred.
    """
    if not self._search_ordered:
        self._search_ordered = len(self._search_terms) > 0
    return super(SearchableQuerySet, self).order_by(*field_names)

def annotate_scores(self):
    """
    If search has occurred and no ordering has occurred, decorate
    each result with the number of search terms so that it can be
    sorted by the number of occurrence of terms.

    In the case of search fields that span model relationships, we
    cannot accurately match occurrences without some very
    complicated traversal code, which we won't attempt. So in this
    case, namely when there are no matches for a result (count=0),
    and search fields contain relationships (double underscores),
    we assume one match for one of the fields, and use the average
    weight of all search fields with relationships.
    """
    results = super(SearchableQuerySet, self).iterator()
    if self._search_terms and not self._search_ordered:
        results = list(results)
        for i, result in enumerate(results):
            count = 0
            related_weights = []
            for (field, weight) in self._search_fields.items():
                if "__" in field:
                    related_weights.append(weight)
                for term in self._search_terms:
                    field_value = getattr(result, field, None)
                    if field_value:
                        count += field_value.lower().count(term) * weight
            if not count and related_weights:
                count = int(sum(related_weights) / len(related_weights))

            if result.publish_date:
                age = (now() - result.publish_date).total_seconds()
                if age > 0:
                    count = count / age**settings.SEARCH_AGE_SCALE_FACTOR

            results[i].result_count = count
        return iter(results)
    return results

修复方案

Django 5.0 中QuerySet._clone()方法不再接受自定义关键字参数,Mezzanine原代码尝试将_search_terms等属性通过参数传递给父类_clone(),导致报错。需修改SearchableQuerySet._clone()方法:

修改前代码

def _clone(self, *args, **kwargs):
    """
    Ensure attributes are copied to subsequent queries.
    """
    for attr in ("_search_terms", "_search_fields", "_search_ordered"):
        kwargs[attr] = getattr(self, attr)
    return super(SearchableQuerySet, self)._clone(*args, **kwargs)

修改后代码

def _clone(self, *args, **kwargs):
    """
    Ensure attributes are copied to subsequent queries.
    """
    clone = super(SearchableQuerySet, self)._clone(*args, **kwargs)
    for attr in ("_search_terms", "_search_fields", "_search_ordered"):
        setattr(clone, attr, getattr(self, attr))
    return clone

原理说明

先调用父类_clone()方法创建克隆对象,再通过setattr()手动将自定义属性赋值给克隆实例,而非通过参数传递,适配Django 5.0的API变更。

内容的提问来源于stack exchange,提问作者Nathan Maier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:42:32