Mixer.blend() module cannot properly create an instance that is referencing custom User object with UUID as PK

1.8k Views Asked by At

I am trying to run tests of my models using pytest. I am having trouble with using mixer on a SQLite3 in ":memory:" database.

I have a model named Category that has a foreign key attribute called "created_by" which references my custom User model.

When assigning the referenced model of User to the category variable in mixer.blend() I get an exception that says that the User instance is not that of a UUID.

See my test below:

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'

This is very strange as this worked previously (about a week ago). Why can mixer not coerce the UUID from the primary key of my User object?

If anyone has any ideas I am all ears. Below I define my models and show a few stack traces. Hopefully, we can find some insight.

Here is the stack trace in pytest:

self = <django.db.models.fields.UUIDField: id>, value = <User: [email protected]>

    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: [email protected]>, 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='\x12\x34\x56\x78'*4)
        UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
                      '\x12\x34\x56\x78\x12\x34\x56\x78')
        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: [email protected]>

   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: ["'[email protected]' is not a valid UUID."]

venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2330: ValidationError

See this last section of code in the above stack trace:

I found that if I change "value" in the try block to "value.id" that the exception is not thrown.

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: 
["'[email protected]' is not a valid UUID."]

My User model is using UUID as its primary key.

Here are the two models (Category and User) that I am using.

My abstract BaseModel inheritance in Category just includes basic information like created_date, is_active, etc.

Category:

# 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)

Here is my User model:

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)

I popped into the Django shell and had the same issues with mixer so this is not an issue with 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: ["'[email protected]' is not a valid UUID."]
1

There are 1 best solutions below

0
On

It seems that this is a versioning issue as Mixer does not support Django 2.1 yet.

This PR supposedly will fix the issue. Not sure when or if it will be merged, as there have been no updates to the Mixer repo for 5 months.

Mixer PR for Django 2.1 Support on Github