【问题标题】:How Can You Create an Admin User with Factory_Boy?如何使用 Factory_Boy 创建管理员用户?
【发布时间】:2013-03-15 00:30:21
【问题描述】:

我是一个相对的 Django 初学者,刚刚开始为我的项目做一些测试。我想要做的是使用登录到 Django Admin 站点的 selenium 构建一个功能测试。

我首先遵循本教程http://www.tdd-django-tutorial.com/tutorial/1/ 并使用fixtures 和dumpdata 使管理员帐户信息可用于测试应用程序(这将创建一个新数据库)。这很好用。

然后我想看看是否可以使用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 做到这一点?有可能还是我需要为此使用固定装置?

(在这篇文章中,有些人建议固定装置是必要的,但工厂男孩没有出现:How to create admin user in django tests.py。我还尝试了在同一答案底部建议的解决方案:https://stackoverflow.com/a/3495219/1539688。它不起作用我……)

【问题讨论】:

    标签: python django selenium factory-boy


    【解决方案1】:

    如果你继承 factory.DjangoModelFactory 它应该为你保存用户对象。请参阅PostGenerationMethodCall 下的注释部分。那么你只需要做到以下几点:

    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
    

    【讨论】:

    【解决方案2】:

    我正在使用 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
    amber60@hotmail.com
    

    使用您从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)
    

    就是这样,使用示例中相同的逻辑来创建您的超级用户。

    【讨论】:

    • 我会将密码行更改为password = factory.LazyFunction(lambda: make_password('pi3.1415')) 这可能永远不会重要,但是对于您的版本,make_password 只评估一次,因此所有生成的用户都将拥有相同的散列密码。将其包装在 lamdba 中将导致它在每次调用时重新生成密码,从而确保实例之间的实际密码哈希值不同。
    • @brocksamson,请随意。我只是这样添加它,因为我需要知道我的用户的密码。
    • 我认为我的评论不是很清楚。在这两种情况下,密码都是已知的,它只是关于密码的实际散列值。 IE。如果您使用您的版本创建 100 个用户,所有用户都将拥有相同的密码和密码哈希。我的版本,所有用户都将拥有相同的密码,但密码哈希会不同。我认为唯一重要的是在某些身份验证测试中,因此您的版本几乎适用于每个用例。只是想注意两者之间的区别。
    • @brocksamson,这是一个非常好的观点!我马上更新我的答案。
    【解决方案3】:

    我假设您正在编写 http://www.tdd-django-tutorial.com 教程,因为这也是我被卡住的地方。您现在可能已经想通了,但是对于下一个人来说,这是对我有用的代码,诀窍是添加 _prepare 方法以确保密码被加密,并将所有标志设置为 true(这是使用 Django 1.5 完成的。 1,如果您使用的是早期版本,请更改 User 模型导入)

    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!')
    

    【讨论】:

      【解决方案4】:

      创建管理员用户:

      您可以添加一个 Params 声明,以便灵活地快速创建管理员用户或普通用户。

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

      设置原始密码:

      为了确保密码参数被设置为原始未加密值,您可以在 _create 类方法覆盖的初始保存后使用 Django 的 set_password 设置原始密码。

      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
      

      用法:

      # 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

      【讨论】:

        【解决方案5】:

        我在创建用户时遇到了类似的问题。创建用户时使用 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 方法对其进行哈希处理。在测试中,您可以创建一个没有散列值的 var,并使用这个 var 创建一个用户。 示例:

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

        【讨论】:

          【解决方案6】:

          我认为这样你可以保留你在代码中所期望的行为,这样你就有一个默认的密码值,你也可以在调用 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
          

          【讨论】:

            猜你喜欢
            • 2014-03-12
            • 2015-04-21
            • 2019-05-17
            • 1970-01-01
            • 1970-01-01
            • 2012-08-02
            • 1970-01-01
            • 2021-12-22
            • 1970-01-01
            相关资源
            最近更新 更多