Custom User factory fails when using a CustomUserManager in Django 2
See original GitHub issueDescription
Trying to use a factory to create a user instance for a user model that extends AbstractUser
and implements CustomUserManager
results in a TypeError
.
To Reproduce
- Implement a
CustomUserManager
in the way recommended here for a customUser
model that extends theAbstractUser
base class and doesn’t override theusername
field. Add a field calledid
. https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#django.contrib.auth.models.CustomUserManager - Create a factory for that
User
model and write a@classmethod
as suggested in the FactoryBoy documentation: https://factoryboy.readthedocs.io/en/latest/recipes.html#custom-manager-methods - Try to create a new
User
using theUserFactory
you just created.
Model / Factory code
# Factories
class CompanyToProfileFactory(factory.DjangoModelFactory):
"""Factory for `client.CompanyToProfile` Django model."""
class Meta:
model = models.CompanyToProfile
company = factory.SubFactory(CompanyFactory)
profile = factory.SubFactory(ProfileFactory)
access = factory.SubFactory(AccessFactory)
created = factory.Faker("past_datetime", tzinfo=pytz.UTC)
updated = factory.Faker("past_datetime", tzinfo=pytz.UTC)
class ProfileFactory(factory.DjangoModelFactory):
"""Factory for `personal.Profile` Django model."""
class Meta:
model = models.Profile
class Params:
superuser = factory.Trait(is_admin=True, is_superuser=True, is_staff=True)
id = factory.Faker("uuid4")
title = factory.SubFactory(TitleFactory)
initials = factory.Faker("word")
date_of_birth = factory.Faker("past_date")
email = factory.Faker("email")
gender = factory.SubFactory(GenderFactory)
ethnicity = factory.SubFactory(EthnicityFactory)
phone_number = factory.Faker("phone_number")
mobile_number = factory.Faker("phone_number")
profile_type = factory.SubFactory(ProfileTypeFactory)
created = factory.Faker("past_datetime", tzinfo=pytz.UTC)
updated = factory.Faker("past_datetime", tzinfo=pytz.UTC)
@classmethod
def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with our custom call."""
manager = cls._get_manager(model_class)
# The default would use ``manager.create(*args, **kwargs)``
return manager.create_user(*args, **kwargs)
# model and manager
class ProfileManager(BaseUserManager):
def create_user(self, username='', email='', password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
try:
email = validate_email(email)
except ValidationError:
raise ValueError('Invalid email address')
user = self.model(
username=username,
email=self.normalize_email(email),
)
user.save(using=self._db)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username='', email='', password=''):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
if not password:
raise ValueError('SuperUser must have password')
user = self.create_user(
username=username,
email=email,
password=password
)
user.is_admin = True
user.is_superuser = True
user.is_staff = True
user.email = email
user.save(using=self._db)
return user
class Profile(AbstractUser):
"""Basic information about a person that uses the system."""
objects = ProfileManager()
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# slug = AutoSlugField(populate_from=['first_name', 'initials', 'last_name', 'date_of_birth'])
title = models.ForeignKey(Title, null=True, on_delete=models.DO_NOTHING)
# first_name
initials = models.CharField(max_length=20, null=True, blank=True, default=None)
# last_name
date_of_birth = models.DateField(null=True)
email = models.EmailField(_('email address'), blank=True, unique=True)
gender = models.ForeignKey(Gender, null=True, on_delete=None)
ethnicity = models.ForeignKey(Ethnicity, null=True, on_delete=None)
phone_number = models.CharField(max_length=15, null=True, blank=True)
mobile_number = models.CharField(max_length=15, null=True, blank=True)
# profile type
profile_type = models.ForeignKey(ProfileType, null=True, default=None, on_delete=SET_NULL)
# Administrative Fields
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['last_name']
indexes = [
models.Index(fields=['phone_number']),
models.Index(fields=['mobile_number']),
models.Index(fields=['email']),
models.Index(fields=['first_name', 'last_name']),
]
class CompanyToProfile(models.Model):
"""Relationship of a user to the company"""
company = models.ForeignKey(Company, on_delete=CASCADE)
profile = models.ForeignKey(Profile, on_delete=CASCADE)
access = models.ForeignKey(Access, null=True, default=None, on_delete=CASCADE)
# Administrative Fields
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.profile.get_full_name()} - {self.access.access} - {self.company.company}'
The issue
Trying to create an object using a factory that relates in any way to the custom User
factory (in this case ProfileFactory
which is a SubFactory
of CompanyToProfileFactory
(see code below) will cause a TypeError
(see error below)
# code that causes exception:
class CompanyToProfileTestCase(TestCase):
"""Tests for `client.CompanyToProfile` Django model."""
def setUp(self):
self.company_to_profile = CompanyToProfileFactory()
# error from running pytest in CLI:
@classmethod
def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with our custom call."""
manager = cls._get_manager(model_class)
# The default would use ``manager.create(*args, **kwargs)``
> return manager.create_user(*args, **kwargs)
E TypeError: create_user() got an unexpected keyword argument 'id'
Issue Analytics
- State:
- Created 4 years ago
- Comments:6 (2 by maintainers)
Top Results From Across the Web
Django custom user error - Stack Overflow
It looks like your indentation is wrong. get_by_natural_key() is a function, not a method of your manager class.
Read more >Customizing authentication in Django
This document provides details about how the auth system can be customized. Authentication backends provide an extensible system for when a username and ......
Read more >What You Need to Know to Manage Users in Django Admin
User management in Django admin is a tricky subject. If you enforce too many permissions, then you might interfere with day-to-day operations.
Read more >Creating a Custom User Model (Django) - YouTube
Free website building course with Django & Python: https://codingwithmitch.com/courses/building-a-website- django -python/In this video I show ...
Read more >how to avoid repetition in custom User manager testing-django
You can use a helper method and a fixture to reduce the repeated code. ... import UserFactory pytestmark = pytest.mark.django_db class TestsUsersManagers: ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
The issue with your code is that your
create_user
method only accepts a subset of the fields defined in your factory (username
,email
andpassword
).However, you call it with the set of every field defined in your factory:
The cleanest solution to your problem would be to adjust your
create_user
function to accept al possible fields, as stated in the django docs section that you mentioned (emphasis mine):This would be:
Btw, I did not have to do anything different to the
_create()
method in the factory: