Выбрать тестовую базу данных?


Я пытаюсь бежать

./manage.py test

Но это говорит мне

Получена ошибка при создании тестовой базы данных: отказано в разрешении на создание базы данных

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

Итак, не могу ли я создать тестовую базу данных вручную и вместо того, чтобы сказать Джанго смывать его каждый раз, вместо того, чтобы воссоздавать все это?

10 18

10 ответов:

У меня была похожая проблема. Но я хотел, чтобы Django просто обошел создание тестовой базы данных для одного из моих экземпляров (это не зеркало жестко). Следуя предложению Марка, я создал пользовательский тестовый раннер, как показано ниже

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

Затем я создал дополнительную запись dict в одной из моих записей базы данных внутри settings.py, 'BYPASS_CREATION':'yes',

Наконец, я настроил новый Тестраннер с

TEST_RUNNER = 'auth.data.runner.ByPassableDBDjangoTestSuiteRunner'

Я бы предложил использовать sqlite3 для тестирования, продолжая использовать mysql / postgres/etc для производства.

Это можно сделать, поместив это в файл настроек:

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

Смотрите запуск тестов django с помощью sqlite

Временный файл базы данных sqlite будет создан в вашем домашнем проекте django, к которому у вас будет доступ на запись. Другое преимущество заключается в том, что sqlite3 гораздо быстрее тестируется. Однако вы можете столкнуться с проблемами, если используете какие-либо mysql / postgres специфический raw sql (которого вы должны стараться избегать в любом случае).

Я думаю, что лучшим решением может быть определение вашего собственного тестового бегуна.

Я добавил Это в комментарии выше, но это было потеряно-последние изменения в webfaction делают это намного проще. Теперь вы можете создаватьновые частные экземпляры баз данных .

Следуйте инструкциям там, и при создании нового пользователя обязательно дайте ему разрешение на ALTER USER new_username CREATEDB;.

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

Вы можете использовать django-nose в качестве TEST_RUNNER. После установки, если вы передадите следующую переменную среды, она не будет удалять и повторно создавать базу данных (сначала создайте ее вручную самостоятельно).

REUSE_DB=1 ./manage.py test

Вы также можете добавить следующее: settings.py таким образом, вам не нужно писать REUSE_DB=1 каждый раз, когда вы хотите запустить тесты:

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

Примечание: это также оставит все ваши таблицы в базах данных, что означает, что установка теста будет немного быстрее, но вы вам придется вручную обновить таблицы (или удалить и заново создать базу данных самостоятельно) при изменении ваших моделей.

Мой вариант повторного использования базы данных:

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)

Ниже приведен набор тестов django runner для создания базы данных с использованиемWebfaction XML-RPC API . Обратите внимание, что настройка базы данных с помощью API может занять до минуты, и сценарий может показаться застрявшим на мгновение, просто подождите некоторое время.

Примечание: существует риск безопасности, связанный с наличием пароля панели управления на сервере webfaction, поскольку кто-то, взломав ваш SSH-сервер, может завладеть вашей учетной записью Webfaction. Если это вызывает беспокойство, установите USE_SESSKEY в True и используйте сценарий структуры ниже этого сценария, чтобы передать идентификатор сеанса серверу. Ключ сеанса истекает через 1 час от последнего вызова API.

Файл test_runner.py: в сервере, который нужно настроить ./manage.py тест на использование 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)

Принятый ответ не сработал для меня. Он настолько устарел, что не работал на моей устаревшей кодовой базе с djano 1.5.

Я написал пост в блоге , полностью описывающий, как я решил эту проблему, создав альтернативный тестовый раннер и изменив настройки django, чтобы обеспечить всю необходимую конфигурацию и использовать новый тестовый раннер.

Измените следующие методы в 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, если они вам нужны.

Простое решение : измените TEST_DATABASE_PREFIX в django/db/backends/base/creation.py, Как вам нравится.