Django升级到3.2后,通过自定义UUIDField进行过滤

mzaanser  于 2023-04-13  发布在  Go
关注(0)|答案(2)|浏览(138)

我有一个Django项目,我最近从Django 2.2升级到了3.2。在这个项目中,我使用了一个自定义的UUIDField,它将UUID保存为char(36),格式如下:12345678-1234-5678-1234-567812345678

import uuid

from django.db import models

class UUIDField(models.UUIDField):
    """
    Overrides Django UUIDField to store full UUID's including dashes.
    """
    def __init__(self, verbose_name=None, **kwargs):
        super().__init__(verbose_name, **kwargs)
        self.max_length = 36

    def get_internal_type(self):
        return "CharField"

    def get_db_prep_value(self, value, connection, prepared=False):
        if value is None:
            return None
        if not isinstance(value, uuid.UUID):
            try:
                value = uuid.UUID(value)
            except AttributeError:
                raise TypeError(self.error_messages['invalid'] % {'value': value})

        if connection.features.has_native_uuid_field:
            return value
        return str(value)

升级后,我注意到搜索完整的UUID不再起作用。如果我只提供UUID的第一部分(直到第一个连字符后的第一个字符),它会像预期的那样工作。

Python 3.6.9 (default, Mar 15 2022, 13:55:28)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from foobar.foo.models import Foo
>>>
>>> Foo.objects.all()
<QuerySet [<Foo: Foo object (34c46fe8-caf0-11ec-bdb9-482ae362a4c0)>]>
>>>
>>> Foo.objects.filter(id__icontains='34c46fe8-caf0-11ec-bdb9-482ae362a4c0')
<QuerySet []>
>>>
>>> Foo.objects.filter(id__icontains='34c46fe8-')
<QuerySet [<Foo: Foo object (34c46fe8-caf0-11ec-bdb9-482ae362a4c0)>]>
>>>
>>> Foo.objects.filter(id__icontains='34c46fe8-c')
<QuerySet []>
>>>

我已经尝试过UUIDField方法,但我似乎找不出哪里出错了。这里有一个链接,指向一个使用简化模型的Gist,我从那里得到了上面的shell示例。

s3fp2yjn

s3fp2yjn1#

经过长时间的pdb调试,我终于找到了问题所在。我希望在调试过程中找到原始的SQL片段,但一个名为WhereNode的对象引起了我的注意。

> /home/milanb/temp/django_custom_uuid_field/django_playground/lib/python3.6/site-packages/django/db/models/sql/query.py(1399)build_filter()
-> return clause, used_joins if not require_outer else ()
(Pdb) n
--Return--
> /home/milanb/temp/django_custom_uuid_field/django_playground/lib/python3.6/site-packages/django/db/models/sql/query.py(1399)build_filter()->(<WhereNode: (...f9c4f880be0>)>, {'foo_foo'})
-> return clause, used_joins if not require_outer else ()
(Pdb) retval
(<WhereNode: (AND: <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>)>, {'foo_foo'})
(Pdb) pp locals()
{'__return__': (<WhereNode: (AND: <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>)>,
                {'foo_foo'}),
 'alias': 'foo_foo',
 'allow_joins': True,
 'allow_many': True,
 'arg': 'id__icontains',
 'branch_negated': False,
 'can_reuse': {'foo_foo'},
 'check_filterable': True,
 'clause': <WhereNode: (AND: <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>)>,
 'col': Col(foo_foo, foo.Foo.id),
 'condition': <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>,
 'current_negated': False,
 'filter_expr': ('id__icontains', '34c46fe8-caf0-11ec-bdb9-482ae362a4c0'),
 'join_info': JoinInfo(final_field=<foobar.foo.fields.UUIDField: id>, targets=(<foobar.foo.fields.UUIDField: id>,), opts=<Options for Foo>, joins=['foo_foo'], path=[], transform_function=<function Query.setup_joins.<locals>.final_transformer at 0x7f9c4f89ca60>),
 'join_list': ['foo_foo'],
 'lookup_type': 'icontains',
 'lookups': ['icontains'],
 'opts': <Options for Foo>,
 'parts': ['id'],
 'pre_joins': {},
 'reffed_expression': False,
 'require_outer': False,
 'reuse_with_filtered_relation': False,
 'self': <django.db.models.sql.query.Query object at 0x7f9c4f902c18>,
 'split_subq': True,
 'targets': (<foobar.foo.fields.UUIDField: id>,),
 'used_joins': {'foo_foo'},
 'value': '34c46fe8-caf0-11ec-bdb9-482ae362a4c0'}
(Pdb) pp clause
<WhereNode: (AND: <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>)>

我注意到这一行:

'clause': <WhereNode: (AND: <django.db.models.lookups.UUIDIContains object at 0x7f9c4f880be0>)>,

我不知道django.db.models.lookups.UUIDIContains对象是什么,所以直接去Django的源代码找答案。这是一个空类,由两个mixin IContainsUUIDTextMixin组成。在我的例子中,后者是罪魁祸首,因为它从查找值中删除了所有连字符。这是2.2版之后的introduced as a fix,我在升级之前使用的,为了支持没有本地UUID类型的数据库后端(例如,在我的情况下,MySQL 5.7)。
修复非常简单,我只需要注册一个自定义的UUIDIContains类,而不需要为我的自定义UUIDField注册UUIDTextMixin
我的新fields.py

import uuid

from django.db import models
from django.db.models.lookups import IContains

class UUIDField(models.UUIDField):
    """
    Overrides Django UUIDField to store full UUID's including dashes.
    """
    def __init__(self, verbose_name=None, **kwargs):
        super().__init__(verbose_name, **kwargs)
        self.max_length = 36

    def get_internal_type(self):
        return "CharField"

    def get_db_prep_value(self, value, connection, prepared=False):
        if value is None:
            return None
        if not isinstance(value, uuid.UUID):
            try:
                value = uuid.UUID(value)
            except AttributeError:
                raise TypeError(self.error_messages['invalid'] % {'value': value})

        if connection.features.has_native_uuid_field:
            return value
        return str(value)

@UUIDField.register_lookup
class UUIDIContains(IContains):
    pass
ruyhziif

ruyhziif2#

米洛得到了答案,但在我的情况下,我也不得不这样做iexact查找。我使用MSSQL原生uuid,通过自定义UUID字段,因为没有后端支持原生版本。但MSSQL不支持无dash版本的uuid。
我的UUIDField略有不同,但我还必须添加注册IExact查找,而不使用UUIDTextMixin。

@UUIDField.register_lookup
class UUIDIExact(IExact):
    pass

相关问题