[Django]-How Can You Create an Admin User with Factory_Boy?

55👍

If you subclass factory.DjangoModelFactory it should save the user object for you. See the note section under PostGenerationMethodCall. Then you only need to do the following:

class UserFactory(factory.DjangoModelFactory):
    FACTORY_FOR = User

    email = 'admin@admin.com'
    username = 'admin'
    password = factory.PostGenerationMethodCall('set_password', 'adm1n')

    is_superuser = True
    is_staff = True
    is_active = True

13👍

I am using Django 1.11 (I can bet it will work in Django 2+) and factory_boy 2.11.1. This was pretty simple:

import factory
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import User


class SuperUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    username = factory.Faker('email')
    password = factory.LazyFunction(lambda: make_password('pi3.1415'))
    is_staff = True
    is_superuser = True

In this example, all users will have password 'pi3.1415' change it accordingly if you want something different, or you can even use password = factory.Faker('password') to generate a random password (however, it should be something you are able to figure out. Otherwise, it will be very hard to log in).

Example creating a superuser

>>> user = SuperUserFactory.create()
>>> user.username # the following output will be different in your case
amber60@hotmail.com

Use the email you got from user.username and the password 'pi3.1415' to log in in the admin.

What if the user has reverse foreign keys associated?

Simple, let’s say you have a model Profile which has a foreign key to your User model. Then you have to add the following classes:

class Profile(models.Model):
    user = models.OneToOneField(User)
    visited = models.BooleanField(default=False)


# You need to set the foreign key dependency using factory.SubFactory
class ProfileFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Profile

    user = factory.SubFactory(UserFactory)


# use a RelatedFactory to refer to a reverse ForeignKey 
class SuperUserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    username = factory.Faker('email')
    password = factory.LazyFunction(lambda: make_password('pi3.1415'))
    is_staff = True
    is_superuser = True
    profile = factory.RelatedFactory(ProfileFactory, 'user', visited=True)

That’s it, use the same logic in the example to create your superuser.

8👍

Creating admin users:

You can add a Params declaration that provides flexibility to quickly create an admin user or a normal user.

https://factoryboy.readthedocs.io/en/latest/introduction.html?highlight=class%20Params#altering-a-factory-s-behaviour-parameters-and-traits

Setting raw passwords:

In order to ensure that the password param gets set as a raw unencrypted value, you can use Django’s set_password to set a raw password after the initial save in the _create classmethod override.

https://docs.djangoproject.com/en/2.1/ref/contrib/auth/#django.contrib.auth.models.User.set_password

class UserFactory(factory.django.DjangoModelFactory):
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    username = factory.Sequence(lambda n: 'demo-user-%d' % n)
    is_staff = False
    is_superuser = False
    password = 'secret'

    @factory.lazy_attribute
    def email(self):
        return '%s@test.com' % self.username

    class Meta:
        model = User

    class Params: 
        # declare a trait that adds relevant parameters for admin users
        flag_is_superuser = factory.Trait(
            is_superuser=True,
            is_staff=True,
            username = factory.Sequence(lambda n: 'admin-%d' % n),
        )

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        password = kwargs.pop("password", None)
        obj = super(UserFactory, cls)._create(model_class, *args, **kwargs)
        # ensure the raw password gets set after the initial save
        obj.set_password(password)
        obj.save()
        return obj

Usage:

# This admin user can log in as "admin-1", password "secret"
admin = UserFactory.create(flag_is_superuser=True)

# This regular user can log in as "userABC", password "secretABC"
user = UserFactory.create(username="userABC", password="secretABC")

Using factory-boy v2.11.1 and Django v1.11.6

7👍

I’m assuming you’re working on the http://www.tdd-django-tutorial.com tutorial because that’s where I got stuck as well. You probably figured this out by now, but for the next person, here’s the code that worked for me, the trick was adding the _prepare method to ensure password is encrypted, and setting all the flags to true (This was done with Django 1.5.1, if you’re using an earlier version, change the User model imports)

from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

import factory
from django.contrib.auth import get_user_model
User = get_user_model()


class UserFactory(factory.DjangoModelFactory):
    FACTORY_FOR = User

    email = 'admin@admin.com'
    username = 'admin'
    password = 'adm1n'

    is_superuser = True
    is_staff = True
    is_active = True

    @classmethod
    def _prepare(cls, create, **kwargs):
        password = kwargs.pop('password', None)
        user = super(UserFactory, cls)._prepare(create, **kwargs)
        if password:
            user.set_password(password)
            if create:
                user.save()
        return user

class PollsTest(LiveServerTestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()
        self.browser.implicitly_wait(3)
        self.user = UserFactory.create()


    def tearDown(self):
        self.browser.quit()

    def test_can_create_new_poll_via_admin_site(self):
        self.browser.get(self.live_server_url+'/admin/')

        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Django administration', body.text)

        username_field = self.browser.find_element_by_name('username')
        username_field.send_keys(self.user.username)

        password_field = self.browser.find_element_by_name('password')
        password_field.send_keys('adm1n')
        password_field.send_keys(Keys.ENTER)

        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Site administration', body.text)

        polls_links = self.browser.find_element_by_link_text('Polls')
        self.assertEqual(len(polls_links), 2)


        self.fail('Finish the test!')

5👍

I had a similar problem when created a user. Django hash passwords when creates a user and you save the password using DjangoFactory without hash. On login Django checks password that you send with stored hash one. On this step verification fails as you check not hashed password with a not hashed password. Here is an example how I fixed this in my code:

from django.contrib.auth.hashers import make_password
from factory import DjangoModelFactory, Sequence



class UserFactory(DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ('username', 'password')

    username = Sequence(lambda n: 'somename%s' % n)
    password = Sequence(lambda p: 'mysuperpass%s' % p)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        """Override the default ``_create`` with our custom call."""
        kwargs['password'] = make_password(kwargs['password'])
        return super(UserFactory, cls)._create(model_class, *args, **kwargs)

I take password that was generated using Sequence and hash it using Django make_password method. In tests you can create a var with not hashed value and create a user with this var.
Example:

password = 'test123'
user = UserFactory(password=my_password)
👤Andrew

1👍

This way I think you preserve the behavior you expect in your code, this way you have a default value for password and also you can override it with any value you want when calling UserFactory.

class UserFactory(factory.Factory):
    FACTORY_FOR = User

    username = 'jeff'
    password = 'pass'
    is_superuser = True
    
    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        """Create an instance of the model, and save it to the database."""
        if cls._meta.django_get_or_create:
            return cls._get_or_create(model_class, *args, **kwargs)

        manager = cls._get_manager(model_class)
        return manager.create_user(*args, **kwargs) # Just user the create_user method recommended by Django

Leave a comment