Как вы можете создать пользователя-администратора с помощью Factory_Boy?

Я относительный новичок в Django и только начал тестировать свои проекты. Что я хочу сделать, так это создать функциональный тест с селеном, который входит на сайт администратора Django.

Сначала я следовал этому руководству http://www.tdd-django-tutorial.com/tutorial/1/ и использовали фикстуры и данные дампа, чтобы сделать информацию об учетной записи администратора доступной для тестируемого приложения (которое создает новую базу данных). Это прекрасно работает.

Затем я хотел посмотреть, смогу ли я сделать то же самое, используя factory-boy для замены приборов. Фабричный мальчик создает экземпляр необходимого объекта в файлеtests.py, что мне кажется более понятным. Почему-то я не могу заставить это работать, и документация Factory_boy не слишком полезна...

Вот мои тесты.py

from django.test import LiveServerTestCase
from django.contrib.auth.models import User
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import factory

class UserFactory(factory.Factory):
    FACTORY_FOR = User

    username = 'jeff'
    password = 'pass'
    is_superuser = True

class AdminTest(LiveServerTestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()

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

    def test_if_admin_login_is_possible(self):
        jeff = UserFactory.create()

        # Jeff opens the browser and goes to the admin page
        self.browser = webdriver.Firefox()
        self.browser.get(self.live_server_url + '/admin/')

        # Jeff sees the familiar 'Django Administration' heading
        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Django administration', body.text)

        # Jeff types in his username and password and hits return
        username_field = self.browser.find_element_by_name('username')
        username_field.send_keys(jeff.username)
        password_field = self.browser.find_element_by_name('password')
        password_field.send_keys(jeff.password)
        password_field.send_keys(Keys.RETURN)

        # Jeff finds himself on the 'Site Administration' page
        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Site administration', body.text)

        self.fail('Fail...')

Это не позволяет войти в систему, поскольку каким-то образом он не создает действующую учетную запись администратора. Как я могу сделать это, используя factory-boy? Возможно ли это или мне нужно использовать приспособления для этого?

(В этом посте некоторые люди предположили, что необходимы приспособления, но фабричный мальчик не придумал: Как создать пользователя-администратора в djangotests.py. Я также попробовал решение, предложенное внизу в том же ответе: https://stackoverflow.com/a/3495219/1539688. У меня это не сработало...)


person Brian Fabian Crain    schedule 25.03.2013    source источник


Ответы (6)


Если вы создадите подкласс factory.DjangoModelFactory, он должен сохранить для вас пользовательский объект. См. раздел примечаний в разделе PostGenerationMethodCall. Тогда вам нужно сделать только следующее:

class UserFactory(factory.DjangoModelFactory):
    FACTORY_FOR = User

    email = '[email protected]'
    username = 'admin'
    password = factory.PostGenerationMethodCall('set_password', 'adm1n')

    is_superuser = True
    is_staff = True
    is_active = True
person emispowder    schedule 31.07.2013
comment
Как вы можете установить User Groups. - person Shubham; 06.02.2015
comment
Для тех, кто нашел этот пост сейчас, 2.5.0+ теперь игнорирует атрибуты FACTORY_* в пользу определения атрибутов во внутреннем мета-классе: factoryboy.readthedocs.io/en/latest/changelog.html#upgrading - person RevolutionTech; 01.12.2017

Я использую Django 1.11 (могу поспорить, что он будет работать в Django 2+) и factory_boy 2.11.1. Это было довольно просто:

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

В этом примере у всех пользователей будет пароль 'pi3.1415', измените его соответствующим образом, если вы хотите что-то другое, или вы даже можете использовать password = factory.Faker('password') для генерации случайного пароля (однако это должно быть что-то, что вы можете выяснить. В противном случае это будет очень сложно). трудно войти).

Пример создания суперпользователя

>>> user = SuperUserFactory.create()
>>> user.username # the following output will be different in your case
[email protected]

Используйте адрес электронной почты, полученный от user.username, и пароль 'pi3.1415' для входа в систему администратора.

Что делать, если у пользователя есть связанные обратные внешние ключи?

Просто, скажем, у вас есть модель Profile, у которой есть внешний ключ к вашей модели User. Затем вам нужно добавить следующие классы:

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)

Вот и все, используйте ту же логику в примере, чтобы создать своего суперпользователя.

person lmiguelvargasf    schedule 05.04.2019
comment
Я бы изменил строку пароля на password = factory.LazyFunction(lambda: make_password('pi3.1415')) Это, вероятно, никогда не будет иметь значения, но в вашей версии make_password оценивается только один раз, поэтому все сгенерированные пользователи будут иметь один и тот же хешированный пароль. Оборачивая его в лямбду, он будет перегенерировать пароль при каждом вызове, что гарантирует, что фактические хэши паролей будут разными в разных экземплярах. - person brocksamson; 16.04.2019
comment
@brocksamson, не стесняйтесь делать это. Я просто добавляю его таким образом, потому что мне нужно знать пароль моих пользователей. - person lmiguelvargasf; 16.04.2019
comment
Я не думаю, что мой комментарий был очень ясным. Пароль известен в обоих случаях, это просто фактическое хэшированное значение пароля. т.е. если вы создадите 100 пользователей с вашей версией, все пользователи будут иметь одинаковый пароль и хэш пароля. В моей версии у всех пользователей будет одинаковый пароль, но хэш пароля будет разным. Я думаю, что это имеет значение только в некоторых тестах аутентификации, поэтому ваша версия подходит практически для любого случая использования. Просто хотел отметить разницу между ними. - person brocksamson; 16.04.2019
comment
@brocksamson, это очень хороший момент! Я обновляю свой ответ прямо сейчас. - person lmiguelvargasf; 16.04.2019

Я предполагаю, что вы работаете над учебником http://www.tdd-django-tutorial.com, поскольку он где я тоже застрял. Вы, вероятно, уже поняли это, но для следующего человека, вот код, который сработал для меня, хитрость заключалась в добавлении метода _prepare для обеспечения шифрования пароля и установке всех флагов в значение true (это было сделано с Django 1.5. 1, если вы используете более раннюю версию, измените импорт модели пользователя)

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 = '[email protected]'
    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!')
person alexenko    schedule 31.05.2013

Создание пользователей-администраторов:

Вы можете добавить объявление Params, которое обеспечивает гибкость для быстрого создания пользователя-администратора или обычного пользователя.

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

Установка необработанных паролей:

Чтобы гарантировать, что параметр пароля будет установлен как необработанное незашифрованное значение, вы можете использовать set_password Django для установки необработанного пароля после первоначального сохранения в переопределении метода класса _create.

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 '%[email protected]' % 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

Использование:

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

Использование factory-boy v2.11.1 и Django v1.11.6

person g.carey    schedule 07.02.2019

У меня была аналогичная проблема при создании пользователя. Хеш-пароли Django при создании пользователя, и вы сохраняете пароль с помощью DjangoFactory без хэша. При входе в систему Django проверяет пароль, который вы отправляете, с сохраненным хешем. На этом шаге проверка завершается неудачно, так как вы проверяете нехешированный пароль с помощью нехешированного пароля. Вот пример того, как я исправил это в своем коде:

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)

Я беру пароль, сгенерированный с помощью Sequence, и хеширую его методом Django make_password. В тестах вы можете создать переменную с нехешированным значением и создать пользователя с этой переменной. Пример:

password = 'test123'
user = UserFactory(password=my_password)
person Andrew    schedule 17.01.2018

Таким образом, я думаю, вы сохраняете ожидаемое поведение в своем коде, таким образом, у вас есть значение по умолчанию для пароля, а также вы можете переопределить его любым значением, которое хотите, при вызове 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
person Fer Mena    schedule 11.09.2020