【问题标题】:Choose test database?选择测试数据库?
【发布时间】:2011-03-20 06:03:18
【问题描述】:

我正在尝试跑步

./manage.py test

但它告诉我

创建测试数据库时出错:创建数据库的权限被拒绝

显然它没有创建数据库的权限,但我在共享服务器上,所以对此我无能为力。我可以通过控制面板创建一个新的数据库,但我认为没有任何方法可以让 Django 自动完成。

那么,我不能手动创建测试数据库,而是告诉 Django 每次都刷新它,而不是重新创建整个数据库吗?

【问题讨论】:

  • 您的设置文件有一个数据库名称以及该数据库的用户名和密码。用户名和密码是否正确?你真的可以用它们来连接吗?您可以使用该用户名和密码在 Postgres 中创建数据库吗?
  • @S.Lott:是的,用户名和密码是正确的。我可以使用它们来连接和检索行。 syncdb 工作正常;它也可以创建表格。但是不,我很确定用户无法在 postgres 中创建数据库——这就是问题所在,这就是我想手动创建它的原因。
  • 刚刚在 webfaction 论坛上找到了一个解决方案:forum.webfaction.com/viewtopic.php?pid=16919,但我会暂时搁置这个问题,看看是否有人可以为 Django 1.2 提出一个不那么 hacky 的解决方案。
  • "我很确定用户不能在 postgres 中创建数据库" 或者你绝对不能?而且您无法获得创建测试数据库的权限?你问过吗?
  • 最近的网络派系更新使这成为一个更容易的解决方案 - 您现在可以创建一个新的私有数据库实例;查看详情here。按照说明操作,然后添加此附加权限:ALTER USER new_username CREATEDB;

标签: django postgresql django-testing webfaction


【解决方案1】:

我遇到了类似的问题。但我希望 Django 只是绕过为我的一个实例创建测试数据库(这不是镜像很难)。按照 Mark 的建议,我创建了一个自定义测试运行器,如下

from django.test.simple import DjangoTestSuiteRunner


class ByPassableDBDjangoTestSuiteRunner(DjangoTestSuiteRunner):

    def setup_databases(self, **kwargs):
        from django.db import connections
        old_names = []
        mirrors = []

        for alias in connections:
            connection = connections[alias]
            # If the database is a test mirror, redirect its connection
            # instead of creating a test database.
            if connection.settings_dict['TEST_MIRROR']:
                mirrors.append((alias, connection))
                mirror_alias = connection.settings_dict['TEST_MIRROR']
                connections._connections[alias] = connections[mirror_alias]
            elif connection.settings_dict.get('BYPASS_CREATION','no') == 'no':
                old_names.append((connection, connection.settings_dict['NAME']))
                connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive)
        return old_names, mirrors

然后我在 settings.py 中的一个数据库条目中创建了一个额外的 dict 条目,'BYPASS_CREATION':'yes',

最后,我用

配置了一个新的TestRunner
TEST_RUNNER = 'auth.data.runner.ByPassableDBDjangoTestSuiteRunner'

【讨论】:

  • teardown_databases 方法 (docs.djangoproject.com/en/dev/topics/testing/advanced/…) 怎么样?如果您不想创建数据库,此方法是否确保不会删除绕过的数据库?这是更好的添加方法:def teardown_databases(self, old_config, **kwargs): pass_or_whatever_logic_is_suitable
  • 旧线程,但对于任何寻找答案的人......受这篇文章的启发,这里是 django 1.8 的测试运行器,它添加了一个设置,允许您指定用于测试的数据库。 djangosnippets.org/snippets/10544
  • 这条评论^^现在也有点过时了。在代码中间的某个地方,您需要更改 from django.test.utils import dependency_ordered,即便如此,我认为拆解需要一些爱和关注?
  • 编辑:实际上拆解很好,但是 setup_databases 的返回需要不同的返回 - 我放弃了镜像(我不使用它们),并且 dbs 的行需要有 3 个值,其中一个这是应该适当设置的删除标志。也许@powderflask 您可能想为这些创建一个 git 存储库,我们可以更轻松地维护它?
  • @Jmons :我在 github 上创建了一个 repo,其版本适用于 django1.11 和 django2.2 github.com/powderflask/django-usedb-testrunner 没有时间编写测试或做任何花哨的事情 - 但这是个好主意因为要点确实过时了。谢谢。
【解决方案2】:

我建议使用 sqlite3 进行测试,同时继续使用 mysql/postgres/etc 进行生产。

这可以通过将它放在您的设置文件中来实现:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

Running django tests with sqlite

将在您的 django 项目主页中创建一个临时 sqlite 数据库文件,您将拥有该文件的写入权限。另一个优点是 sqlite3 的测试速度要快得多。但是,如果您使用任何 mysql/postgres 特定的原始 sql,您可能会遇到问题(无论如何您都应该尽量避免)。

【讨论】:

  • 它的创建速度确实快了很多,但是当数据超过 max_length 时,您确实会遇到诸如没有错误之类的问题。那太经常咬我了。所有测试都会通过,但回到 postgresql 会惨遭失败。还建议使用'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}
  • 但是 sqlite3 和 postgres 的行为非常不同!您将看到无法在开发中测试的纯生产错误。
【解决方案3】:

我认为更好的解决方案可能是define your own test runner

【讨论】:

    【解决方案4】:

    我将它添加到上面的 cmets 中,但它有点丢失 - 最近对 webfaction 的更改使这变得更加容易。您现在可以创建new private database instances

    按照那里的说明进行操作,并在创建新用户时确保授予他们ALTER USER new_username CREATEDB; 的权限。

    您可能还应该更改默认的 cron 设置,这样他们就不会尝试检查此数据库是否已启动并频繁运行。

    【讨论】:

    • 在 pg_hba.conf 中添加允许用户对测试数据库进行身份验证的行也可能会有所帮助 -- local test_projdb new_username md5 然后重新启动 postgres 可能有助于解决 postgres 中的权限错误。
    【解决方案5】:

    您可以使用django-nose 作为您的 TEST_RUNNER。安装好后,如果传入如下环境变量,不会删除重新创建数据库(先自己手动创建)。

    REUSE_DB=1 ./manage.py test
    

    您还可以将以下内容添加到 settings.py 中,这样您就不必每次要运行测试时都编写 REUSE_DB=1:

    os.environ['REUSE_DB'] = "1"
    

    注意:这也会将您的所有表格留在数据库中,这意味着测试设置会更快一些,但您必须手动更新表格(或更改模型时自行删除并重新创建数据库。

    【讨论】:

      【解决方案6】:

      我重用数据库的变体:

      from django.test.simple import DjangoTestSuiteRunner
      from django.core.management import call_command
      
      
      class TestRunner(DjangoTestSuiteRunner):
          def setup_databases(self, **kwargs):
              from django.db import connections
      
              settings = connections['default'].settings_dict
              settings['NAME'] = settings['TEST_NAME']
              settings['USER'] = settings['TEST_USER']
              settings['PASSWORD'] = settings['TEST_PASSWD']
      
              call_command('syncdb', verbosity=1, interactive=False, load_initial_data=False)
      
          def teardown_databases(self, old_config, **kwargs):
              from django.db import connection
      
              cursor = connection.cursor()
              cursor.execute('show tables;')
              parts = ('DROP TABLE IF EXISTS %s;' % table for (table,) in cursor.fetchall())
              sql = 'SET FOREIGN_KEY_CHECKS = 0;\n' + '\n'.join(parts) + 'SET FOREIGN_KEY_CHECKS = 1;\n'
              connection.cursor().execute(sql)
      

      【讨论】:

        【解决方案7】:

        以下是使用Webfaction XML-RPC API 创建数据库的django 测试套件运行程序。请注意,使用 API 设置数据库可能需要一分钟时间,并且脚本可能会出现暂时卡住的情况,请稍等片刻。

        注意:在 webfaction 服务器中设置控制面板密码存在安全风险,因为有人侵入您的 web 服务器 SSH 可能会接管您的 Webfaction 帐户。如果这是一个问题,请将 USE_SESSKEY 设置为 True 并使用此脚本下方的结构脚本将会话 ID 传递给服务器。上次 API 调用中的会话密钥 expires in 1 hour

        文件test_runner.py:在服务端需要配置./manage.py test才能使用WebfactionTestRunner

        """
        This test runner uses Webfaction XML-RPC API to create and destroy database
        """
        
        # you can put your control panel username and password here.
        # NOTE: there is a security risk of having control panel password in
        # the webfaction server, because someone breaching into your web server
        # SSH could take over your Webfaction account. If that is a concern,
        # set USE_SESSKEY to True and use the fabric script below this script to
        # generate a session.
        
        USE_SESSKEY = True
        # CP_USERNAME = 'webfactionusername' # required if and only if USE_SESSKEY is False
        # CP_PASSWORD = 'webfactionpassword' # required if and only if USE_SESSKEY is False
        
        import sys
        import os
        from django.test.simple import DjangoTestSuiteRunner
        from django import db
        from webfaction import Webfaction
        
        def get_sesskey():
            f = os.path.expanduser("~/sesskey")
            sesskey = open(f).read().strip()
            os.remove(f)
            return sesskey
        
        if USE_SESSKEY:
            wf = Webfaction(get_sesskey())
        else:
            wf = Webfaction()
            wf.login(CP_USERNAME, CP_PASSWORD)
        
        
        def get_db_user_and_type(connection):
            db_types = {
                'django.db.backends.postgresql_psycopg2': 'postgresql',
                'django.db.backends.mysql': 'mysql',
            }
            return (
                connection.settings_dict['USER'],
                db_types[connection.settings_dict['ENGINE']],
            )
        
        
        def _create_test_db(self, verbosity, autoclobber):
            """
            Internal implementation - creates the test db tables.
            """
        
            test_database_name = self._get_test_db_name()
        
            db_user, db_type = get_db_user_and_type(self.connection)
        
            try:
                wf.create_db(db_user, test_database_name, db_type)
            except Exception as e:
                sys.stderr.write(
                    "Got an error creating the test database: %s\n" % e)
                if not autoclobber:
                    confirm = raw_input(
                        "Type 'yes' if you would like to try deleting the test "
                        "database '%s', or 'no' to cancel: " % test_database_name)
                if autoclobber or confirm == 'yes':
                    try:
                        if verbosity >= 1:
                            print("Destroying old test database '%s'..."
                                % self.connection.alias)
                        wf.delete_db(test_database_name, db_type)
                        wf.create_db(db_user, test_database_name, db_type)
                    except Exception as e:
                        sys.stderr.write(
                            "Got an error recreating the test database: %s\n" % e)
                        sys.exit(2)
                else:
                    print("Tests cancelled.")
                    sys.exit(1)
        
            db.close_connection()
            return test_database_name
        
        
        def _destroy_test_db(self, test_database_name, verbosity):
            """
            Internal implementation - remove the test db tables.
            """
            db_user, db_type = get_db_user_and_type(self.connection)
            wf.delete_db(test_database_name, db_type)
            self.connection.close()
        
        
        class WebfactionTestRunner(DjangoTestSuiteRunner):
            def __init__(self, *args, **kwargs):
                # Monkey patch BaseDatabaseCreation with our own version
                from django.db.backends.creation import BaseDatabaseCreation
                BaseDatabaseCreation._create_test_db = _create_test_db
                BaseDatabaseCreation._destroy_test_db = _destroy_test_db
        
                return super(WebfactionTestRunner, self).__init__(*args, **kwargs)
        

        文件 webfaction.py:这是一个 Webfaction API 的瘦包装器,它需要被 test_runner.py(在远程服务器中)和 fabfile.py(在本地机器中)都可以导入

        import xmlrpclib
        
        class Webfaction(object):
            def __init__(self, sesskey=None):
                self.connection = xmlrpclib.ServerProxy("https://api.webfaction.com/")
                self.sesskey = sesskey
        
            def login(self, username, password):
                self.sesskey, _ = self.connection.login(username, password)
        
            def create_db(self, db_user, db_name, db_type):
                """ Create a database owned by db_user """
                self.connection.create_db(self.sesskey, db_name, db_type, 'unused')
        
                # deletes the default user created by Webfaction API
                self.connection.make_user_owner_of_db(self.sesskey, db_user, db_name, db_type)
                self.connection.delete_db_user(self.sesskey, db_name, db_type)
        
            def delete_db(self, db_name, db_type):
                try:
                    self.connection.delete_db_user(self.sesskey, db_name, db_type)
                except xmlrpclib.Fault as e:
                    print 'ignored error:', e
                try:
                    self.connection.delete_db(self.sesskey, db_name, db_type)
                except xmlrpclib.Fault as e:
                    print 'ignored error:', e
        

        文件 fabfile.py:用于生成会话密钥的示例结构脚本,仅当 USE_SESSKEY=True 时才需要

        from fabric.api import *
        from fabric.operations import run, put
        from webfaction import Webfaction
        import io
        
        env.hosts = ["webfactionusername@webfactionusername.webfactional.com"]
        env.password = "webfactionpassword"
        
        def run_test():
            wf = Webfaction()
            wf.login(env.hosts[0].split('@')[0], env.password)
            sesskey_file = '~/sesskey'
            sesskey = wf.sesskey
            try:
                put(io.StringIO(unicode(sesskey)), sesskey_file, mode='0600')
                # put your test code here
                # e.g. run('DJANGO_SETTINGS_MODULE=settings /path/to/virtualenv/python /path/to/manage.py test --testrunner=test_runner.WebfactionTestRunner')
                raise Exception('write your test here')
            finally:
                run("rm -f %s" % sesskey_file)
        

        【讨论】:

          【解决方案8】:

          接受的答案对我不起作用。它已经过时了,无法在我使用 djano 1.5 的旧代码库上运行。

          我通过创建替代测试运行程序并更改 django 设置以提供所有必需的配置并使用新的测试运行程序来编写 blogpost entirely describing how I solved this issue

          【讨论】:

            【解决方案9】:

            修改django/db/backends/creation.py中的以下方法:

            def _destroy_test_db(self, test_database_name, verbosity):
                "Internal implementation - remove the test db tables."
            
                # Remove the test database to clean up after
                # ourselves. Connect to the previous database (not the test database)
                # to do so, because it's not allowed to delete a database while being
                # connected to it.
                self._set_test_dict()
                cursor = self.connection.cursor()
                self.set_autocommit()
                time.sleep(1) # To avoid "database is being accessed by other users" errors.
            
                cursor.execute("""SELECT table_name FROM information_schema.tables WHERE table_schema='public'""")
                rows = cursor.fetchall()
                for row in rows:
                    try:
                        print "Dropping table '%s'" % row[0]
                        cursor.execute('drop table %s cascade ' % row[0])
                    except:
                        print "Couldn't drop '%s'" % row[0] 
            
                #cursor.execute("DROP DATABASE %s" % self.connection.ops.quote_name(test_database_name))
                self.connection.close()
            
            def _create_test_db(self, verbosity, autoclobber):
                "Internal implementation - creates the test db tables."
            
                suffix = self.sql_table_creation_suffix()
            
                if self.connection.settings_dict['TEST_NAME']:
                    test_database_name = self.connection.settings_dict['TEST_NAME']
                else:
                    test_database_name = TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
            
                qn = self.connection.ops.quote_name
            
                # Create the test database and connect to it. We need to autocommit
                # if the database supports it because PostgreSQL doesn't allow
                # CREATE/DROP DATABASE statements within transactions.
                self._set_test_dict()
                cursor = self.connection.cursor()
                self.set_autocommit()
            
                return test_database_name
            
            def _set_test_dict(self):
                if "TEST_NAME" in self.connection.settings_dict:
                    self.connection.settings_dict["NAME"] = self.connection.settings_dict["TEST_NAME"]
                if "TEST_USER" in self.connection.settings_dict:
                    self.connection.settings_dict['USER'] = self.connection.settings_dict["TEST_USER"]
                if "TEST_PASSWORD" in self.connection.settings_dict:
                    self.connection.settings_dict['PASSWORD'] = self.connection.settings_dict["TEST_PASSWORD"]
            

            似乎有效...如果需要,只需将额外设置添加到您的 settings.py

            【讨论】:

            • 如果您没有任何自定义查询,那么针对 SQLite 数据库执行测试并不是一个坏主意。这可能给你一个(有限的)工作。
            • @Manoj:我想过,但这不是一个很好的测试。 PostgreSQL 和 SQLite 在处理序列/约束/fk 的方式上略有不同......我很可能在使用 postgre 时遇到错误,但我的测试通过了,因为我使用的是 sqlite。不好!
            • 永远不要在 sqllite 上运行测试,当它们真的不应该时它们会通过。
            【解决方案10】:

            简单的解决方法:根据需要将TEST_DATABASE_PREFIX 更改为django/db/backends/base/creation.py

            【讨论】:

              【解决方案11】:

              在使用单元测试时,您需要指定一个 sqlite ENGINE。打开 settings.py 并添加紧随其后的 DATABASES 部分:

              import sys
              if 'test' in sys.argv or 'test_coverage' in sys.argv: #Covers regular testing and django-coverage
                  DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
                  DATABASES['default']['NAME'] = ':memory:'
              

              【讨论】:

                猜你喜欢
                • 2012-04-24
                • 2019-12-12
                • 2010-09-06
                • 2019-10-20
                • 2017-05-14
                • 2017-12-04
                • 2015-01-26
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多