Mixer.blend() 模块无法正确创建引用自定义用户对象的实例,并将 UUID 作为 PK



我正在尝试使用 pytest 对我的模型进行测试。我在":memory:"数据库中的SQLite3上使用混合器时遇到问题。

我有一个名为 Category 的模型,该模型具有一个名为"created_by"的外键属性,该属性引用我的自定义用户模型。

将 User 的引用模型分配给 mixer.blend(( 中的类别变量时,我得到一个异常,指出 User 实例不是 UUID 的实例。

请参阅下面的测试:

import pytest
from mixer.backend.django import mixer
from apps.API import models
pytestmark = pytest.mark.django_db

class TestCategory():
def test_model(self):
category = mixer.blend(models.Category)
assert category.pk == 1, 'Should create a Category instance'

这很奇怪,因为这以前(大约一周前(有效。为什么混音器不能从我的用户对象的主键强制使用 UUID?

如果有人有任何想法,我都是耳朵。下面我定义我的模型并显示一些堆栈跟踪。希望我们能找到一些见解。

下面是 pytest 中的堆栈跟踪:

self = <django.db.models.fields.UUIDField: id>, value = <User: doliver@smith.com>
def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
>               return uuid.UUID(value)
venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2325:

self = <[AttributeError("'UUID' object has no attribute 'int'") raised in repr()] UUID object at 0x7feeebf18f98>, hex = <User: doliver@smith.com>, bytes = None, bytes_le = None
fields = None, int = None, version = None
def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
int=None, version=None):
r"""Create a UUID from either a string of 32 hexadecimal digits,
a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
in little-endian order as the 'bytes_le' argument, a tuple of six
integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
the 'fields' argument, or a single 128-bit integer as the 'int'
argument.  When a string of hex digits is given, curly braces,
hyphens, and a URN prefix are all optional.  For example, these
expressions all yield the same UUID:
UUID('{12345678-1234-5678-1234-567812345678}')
UUID('12345678123456781234567812345678')
UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
UUID(bytes='x12x34x56x78'*4)
UUID(bytes_le='x78x56x34x12x34x12x78x56' +
'x12x34x56x78x12x34x56x78')
UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
UUID(int=0x12345678123456781234567812345678)
Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
be given.  The 'version' argument is optional; if given, the resulting
UUID will have its variant and version set according to RFC 4122,
overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
"""
if [hex, bytes, bytes_le, fields, int].count(None) != 4:
raise TypeError('one of the hex, bytes, bytes_le, fields, '
'or int arguments must be given')
if hex is not None:
>       hex = hex.replace('urn:', '').replace('uuid:', '')
E       AttributeError: 'User' object has no attribute 'replace'
/usr/lib/python3.6/uuid.py:137: AttributeError
During handling of the above exception, another exception occurred:
self = <apps.API.tests.test_models.TestCategory object at 0x7feeef113a58>
def test_model(self):
>       category = mixer.blend(models.Category)
apps/API/tests/test_models.py:13:
venv/lib/python3.6/site-packages/mixer/main.py:568: in blend
return type_mixer.blend(**values)
venv/lib/python3.6/site-packages/mixer/main.py:116: in blend
for name, value in defaults.items()
venv/lib/python3.6/site-packages/mixer/main.py:116: in <genexpr>
for name, value in defaults.items()
venv/lib/python3.6/site-packages/mixer/mix_types.py:222: in gen_value
return type_mixer.gen_field(field)
venv/lib/python3.6/site-packages/mixer/backend/django.py:270: in gen_field
return super(TypeMixer, self).gen_field(field)
venv/lib/python3.6/site-packages/mixer/main.py:193: in gen_field
return self.gen_value(field.name, field, unique=unique)
venv/lib/python3.6/site-packages/mixer/main.py:261: in gen_value
return self.get_value(field_name, value)
venv/lib/python3.6/site-packages/mixer/backend/django.py:218: in get_value
return self._get_value(name, value, field)
venv/lib/python3.6/site-packages/mixer/backend/django.py:233: in _get_value
value = field.scheme.to_python(value)
venv/lib/python3.6/site-packages/django/db/models/fields/related.py:874: in to_python
return self.target_field.to_python(value)

self = <django.db.models.fields.UUIDField: id>, value = <User: doliver@smith.com>
def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
return uuid.UUID(value)
except (AttributeError, ValueError):
raise exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
>                   params={'value': value},
)
E               django.core.exceptions.ValidationError: ["'doliver@smith.com' is not a valid UUID."]
venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2330: ValidationError

请参阅上述堆栈跟踪中的最后一段代码:

我发现如果我将 try 块中的"值"更改为"value.id",则不会引发异常。

def to_python(self, value):
if value is not None and not isinstance(value, uuid.UUID):
try:
return uuid.UUID(value)
except (AttributeError, ValueError):
raise exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
>                   params={'value': value},
)
E               django.core.exceptions.ValidationError: 
["'doliver@smith.com' is not a valid UUID."]

我的用户模型使用 UUID 作为其主键。

这是我正在使用的两个模型(类别和用户(。

我在类别中的抽象 BaseModel 继承只包括基本信息,如created_date、is_active等。

类别:

# django
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
# models
from apps.API.models.BaseModel import BaseModel

class Category(BaseModel):
CATEGORIES = (
# choices tuples go here
)
category = models.CharField(_('category'),
choices=CATEGORIES,
max_length=80,
blank=False,
db_index=True
)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET(
BaseModel.get_sentinel_user),
blank=False,
editable=False,
db_index=True,
related_name=(
'created_categories')
)
class Meta:
app_label = "API"
verbose_name = _('Category')
verbose_name_plural = _('Categories')
def __str__(self):
return '{}'.format(self.category)

这是我的用户模型:

import uuid
# django
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import AbstractBaseUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
# models
from apps.API.models.BaseModel import BaseModel
from apps.API.manager import UserManager

class User(AbstractBaseUser, PermissionsMixin):
# Use UUID to make the merging of tables much easier if need be.
id = models.UUIDField(primary_key=True,
editable=False,
default=uuid.uuid4,
verbose_name='public identifier',)
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'),
max_length=255,
blank=True)
last_name = models.CharField(_('last name'),
max_length=255,
blank=True)
created_date = models.DateTimeField(_('created date'), 
auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
is_superuser = models.BooleanField(_('superuser'), default=False)
is_staff = models.BooleanField(_('staff'), default=False)

objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
app_label = "API"
verbose_name = _('User')
verbose_name_plural = _('Users')
def __str__(self):
return '{}'.format(self.email)

我弹出了 Django shell,并且在 mixer 上遇到了同样的问题,所以这不是 pytest 的问题。

Traceback (most recent call last):
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site- 
packages/django/db/models/fields/__init__.py", line 2325, in to_python
return uuid.UUID(value)
File "/usr/lib/python3.6/uuid.py", line 137, in __init__
hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'User' object has no attribute 'replace'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 568, in blend
return type_mixer.blend(**values)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 116, in blend
for name, value in defaults.items()
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 116, in <genexpr>
for name, value in defaults.items()
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/mix_types.py", line 222, in gen_value
return type_mixer.gen_field(field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 270, in gen_field
return super(TypeMixer, self).gen_field(field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 193, in gen_field
return self.gen_value(field.name, field, unique=unique)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/main.py", line 261, in gen_value
return self.get_value(field_name, value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 218, in get_value
return self._get_value(name, value, field)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/mixer/backend/django.py", line 233, in _get_value
value = field.scheme.to_python(value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/django/db/models/fields/related.py", line 874, in to_python
return self.target_field.to_python(value)
File "/home/users/myname/dev/blah-django-app/venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py", line 2330, in to_python
params={'value': value},
django.core.exceptions.ValidationError: ["'michaela32@yahoo.com' is not a valid UUID."]

这似乎是一个版本控制问题,因为 Mixer 还不支持 Django 2.1。

这个 PR 应该会解决这个问题。不确定何时或是否会合并,因为 Mixer 存储库已经 5 个月没有更新了。

Github 上支持 Django 2.1 的 Mixer PR

最新更新