20

I'm trying to run

./manage.py test

But it tells me

Got an error creating the test database: permission denied to create database

Obviously it doesn't have permission to create the database, but I'm on a shared server, so there's not much I can do about that. I can create a new database through the control panel but I don't think there's any way I can let Django do it automatically.

So, can't I create the test database manually and instead tell Django to flush it every time, rather than recreating the whole thing?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    Your settings file has a database name and a username and password for that database. Are the username and password correct? Can you really use them to connect? Can you use that username and password to do a CREATE DATABASE in Postgres? – S.Lott Jul 23 '10 at 17:46
  • @S.Lott: Yes, the username and password are correct. I can use them to connect, and retrieve rows. `syncdb` works fine; it can create tables too. But no, I'm pretty sure that user *cannot* create databases in postgres -- that's the problem, and that's why I want to create it manually. – mpen Jul 23 '10 at 17:56
  • Just found a sol'n on the webfaction forums: http://forum.webfaction.com/viewtopic.php?pid=16919 but I'll leave this question open for a bit to see if anyone can suggest a less hacky solution for Django 1.2. – mpen Jul 23 '10 at 18:01
  • "I'm pretty sure that user cannot create databases in postgres" Or you **absolutely can't**? And you cannot get permissions to create a test database? Have you asked? – S.Lott Jul 23 '10 at 18:16
  • @S.Lott: Well, how can I test? I create the DB thru the control panel, and it automatically creates a user too. I just tried dropping the schema with that user, and it can't even do that... and the forum I linked to suggests the same. So I'm 99% sure I can't, but I'm not sure how to explicitly test that. No, I haven't asked the server admins... they've basically said "if you want to do that, install a 2nd copy of postgresql and test on that" which I don't want to do... I'm limited to 80 megs memory already, which I've been pushing to the max as is. – mpen Jul 23 '10 at 18:52
  • Are you aware that the test database has a different name from the database in your settings? – S.Lott Jul 23 '10 at 19:19
  • @S.Lott: Eh? You can choose the test database by setting `TEST_NAME`. How's that relevant? – mpen Jul 24 '10 at 14:06
  • @Mark: The privileges for the test database name could be unset. It could simply be a matter of having no privileges for the `X_TEST` database, where the production database name is `X` (IIRC). – S.Lott Jul 25 '10 at 20:58
  • @S.Lott: But Django attempts to create `X_TEST`... it should set the proper privileges too, or it should use the same privileges as the `X` DB...either way, if it's actually able to create the DB, but it doesn't... I'm pretty sure that qualifies for a bug in Django. – mpen Jul 25 '10 at 23:01
  • @Mark: In MySQL you may not have privileges on `X_TEST`. That's not a Django thing -- that's a MySQL thing. If the DBA who created your user did not give you privileges on your database **and** your test database, then you can't test. Hence my initial question. Do you really have the access you think you have? What privileges do you have on what databases? – S.Lott Jul 26 '10 at 00:35
  • @S.Lott: Yeah, but Django is *creating* the database. If it can *create* the DB, it can probably set the privileges on the DB at the same time. There is no DBA; I can create the databases myself through a rudimentary server control panel, but it doesn't give me any more information beyond "database created w/ username/password". Anyway, I still have no idea how to check what permissions I actually do have and Google is being rather useless too. `\z` doesn't tell me much. – mpen Jul 26 '10 at 02:57
  • @Mark: "If it can create the DB, it can ... set the privileges on the DB at the same time" What? Why are you assuming that? Do you have any facts? That's not how RDBMS's work. You don't magically get privileges because you created a database. They're granted on a database-by-database basis. Try creating the necessary `whatever_TEST` database and giving your Django username permissions on that database. – S.Lott Jul 26 '10 at 10:08
  • @S.Lott: So...you're telling me that its possible to have permissions to create a database, but no permission to create tables, insert rows, or query the database you just created? That just sounds funny to me, but so be it. Anyway, I did create my `whatever_TEST` DB, and it already has all the permissions it needs. I *can* insert tables, rows, and whatever. – mpen Jul 26 '10 at 18:21
  • @Mark: "possible to have permissions to create a database, but no permission to create tables, insert rows, or query the database you just created" Yes. Please read the MYSQL reference on permissions. There are several kinds of permissions, some of which are outside a specific database. Others are specific to a specific database -- by name. Further, Django doesn't create any of this -- it's all part of MySQL. – S.Lott Jul 26 '10 at 19:42
  • Well, it's PostgreSQL we've been talking about. And I know the error isn't caused by Django specifically, but it does execute the SQL, which was my point. It's perfectly capable of executing a "grant x to y" statement, no? Might not succeed, but it can try. I'll read up on permissions when I have some time; I just don't think it'll help me solve this specific problem. – mpen Jul 27 '10 at 01:23
  • 2
    Recent webfaction updates makes this a much easier solution now - you can now create a new private database instance; see details [here](http://docs.webfaction.com/software/private-databases.html#creating-private-postgresql-databases-and-users). Follow the instructions and then add this additional permission: `ALTER USER new_username CREATEDB;` – shapiromatron Jun 05 '13 at 20:56

11 Answers11

14

I had a similar issue. But I wanted Django to just bypass the creation of a test database for one of my instances (it is not a mirror tough). Following Mark's suggestion, I created a custom test runner, as follows

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

Then I created an extra dict entry in one of my databases entries inside settings.py, 'BYPASS_CREATION':'yes',

Finally, I configured a new TestRunner with

TEST_RUNNER = 'auth.data.runner.ByPassableDBDjangoTestSuiteRunner'
Vlad T.
  • 2,568
  • 3
  • 26
  • 40
Thiago Ganzarolli
  • 1,161
  • 12
  • 17
  • 1
    What about teardown_databases method (https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.runner.DiscoverRunner.teardown_databases)? If you don't want to create database does this method ensures that bypassed databases won't be dropped? Is this better to add method: def teardown_databases(self, old_config, **kwargs): pass_or_whatever_logic_is_suitable – Robert Lujo Mar 04 '14 at 16:04
  • 2
    Old thread, but for anyone searching for an answer... inspired by this post, here is a test runner for django 1.8 that adds a setting, allowing you to specify the DB to use for testing. https://djangosnippets.org/snippets/10544/ – powderflask Dec 26 '15 at 20:44
  • This comment ^^ is slightly outdated now too. Somewhere in the middle of that code you need to change `from django.test.utils import dependency_ordered` and even then i think teardown needs some love and attention? – Jmons Apr 01 '19 at 15:45
  • Edit: actually the teardown is fine, but the return of setup_databases needs a different return - i ditched mirrors (I dont use them), and the rows of dbs needs to have 3 values in, one of which is the delete flag which should be set as appropriate. Perhaps @powderflask you might want to make a git repo for these and we could maintain it easier? – Jmons Apr 01 '19 at 15:55
  • 1
    @Jmons : I created a repo on github with a version that is working on django1.11 and django2.2 https://github.com/powderflask/django-usedb-testrunner No time to write tests or do anything fancy - but this was a good idea since the gist was truly out of date. Thanks. – powderflask May 20 '19 at 18:52
10

I would suggest using sqlite3 for testing purposes while keeping on using mysql/postgres/etc for production.

This can be achieved by placing this in your settings file:

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

see Running django tests with sqlite

a temporary sqlite database file will be created in your django project home which you will have write access to. The other advantage is that sqlite3 is much faster for testing. You may however run in to problems if you are using any mysql/postgres specific raw sql (which you should try to avoid anyway).

Community
  • 1
  • 1
Michael Bylstra
  • 5,042
  • 4
  • 28
  • 24
  • 7
    It does create a lot faster, but you really hit problems like no errors when data exceed max_length. That bite me too often. All tests would pass but going back to postgresql it would miserably fail. Would also suggest to use `'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}` – Danosaure Jul 15 '13 at 04:06
  • 2
    But sqlite3 and postgres act very differently! You're going to see production-only errors that you can't test for in development. – rik.the.vik May 28 '17 at 01:54
4

I think a better solution might be to define your own test runner.

bignose
  • 30,281
  • 14
  • 77
  • 110
mpen
  • 272,448
  • 266
  • 850
  • 1,236
3

I added this to the comments above but it got kind of lost - recent changes to webfaction make this MUCH easier. You can now create new private database instances.

Follow the instructions there, and when creating a new user make sure to give them the permission to ALTER USER new_username CREATEDB;.

You probably also should change the default cron settings so they don't try to check if this database is up and runnings as frequently.

shapiromatron
  • 569
  • 6
  • 11
  • Also adding the lines allowing user auth for test database in pg_hba.conf might also help -- `local test_projdb new_username md5` and then restart postgres might help with permission errors in postgres. – Alex Volkov Dec 23 '15 at 18:17
2

my variant to reusing database:

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)
lemanyk
  • 101
  • 1
  • 2
2

You could use django-nose as your TEST_RUNNER. Once installed, if you pass the following environment variable, it will not delete and re-create the database (create it manually yourself first).

REUSE_DB=1 ./manage.py test

You can also add the following to settings.py so you don't have to write REUSE_DB=1 every time you want to run tests:

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

Note: this will also leave all your tables in the databases which means test setup will be a little quicker, but you will have to manually update the tables (or delete and re-create the database yourself) when you change your models.

mhost
  • 6,930
  • 5
  • 38
  • 45
1

The following is a django test suite runner to create database using Webfaction XML-RPC API. Note, setting up the database using the API may take up to a minute, and the script may appear to be stuck momentarily, just wait for a little while.

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 pass a session id to the server. The session key expires in 1 hour from the last API call.

File test_runner.py: in the server, you need to configure ./manage.py test to use 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)

File webfaction.py: this is a thin wrapper for Webfaction API, it need to be importable by both test_runner.py (in the remote server) and the fabfile.py (in the local machine)

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

File fabfile.py: A sample fabric script to generate session key, needed only if 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)
Lie Ryan
  • 62,238
  • 13
  • 100
  • 144
1

The accepted answer didn't work for me. It's so outdated, that it didn't run on my legacy codebase with djano 1.5.

I wrote a blogpost entirely describing how I solved this issue by creating an alternative test runner and changing django settings to provide all the required config and to use new test runner.

HorsePunchKid
  • 848
  • 12
  • 17
1

You need to specify a sqlite ENGINE when using unit tests. Open the settings.py and add the just after DATABASES section:

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:'
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Osoro
  • 31
  • 2
0

Modify the following methods in 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"]

Seems to work... just add the extra settings to your settings.py if you need 'em.

bignose
  • 30,281
  • 14
  • 77
  • 110
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • If you do not have any custom queries it won't be a bad idea to execute the tests against a SQLite database. That *might* give you a (limited) work around. – Manoj Govindan Jul 24 '10 at 06:10
  • @Manoj: I thought about that, but then it's not a very good test. PostgreSQL and SQLite are a little different in how they handle sequences/constraints/fk's... it's very possible that I could have an error w/ postgre but my tests are passing because I'm using sqlite. Not good! – mpen Jul 24 '10 at 14:08
  • never run tests on sqllite, they will pass when they really shouldn't. – Adam Spence Jun 17 '13 at 13:53
0

Simple workaround: change TEST_DATABASE_PREFIX in django/db/backends/base/creation.py as you like.

Francesco Frassinelli
  • 3,145
  • 2
  • 31
  • 43