17 Stimmen

Django-Tests beschweren sich über fehlende Tabellen

Wenn ich meinen Test mit meiner Customer Modell, erhalte ich folgende Fehlermeldung:

DatabaseError: (1146, "Table 'test_mcif2.customer' doesn't exist")

Ich bin nicht völlig überrascht, weil ich mein Django-Projekt mit einer "Legacy"-Datenbank verbunden habe. Da meine Tabellen nicht "auf Django-Art" erstellt wurden, ist es nicht schockierend, dass Django nicht in der Lage ist, mit ihnen zu kommunizieren, ohne etwas zu tricksen. Hier ist mein Modell:

from django.db import models
from django.db import connection, transaction
from mcif.models.mcif_model import McifModel

class Customer(McifModel):

    class Meta:
        db_table = u'customer'
        app_name = 'mcif'

    id = models.BigIntegerField(primary_key=True)
    customer_number = models.CharField(unique=True, max_length=255)
    social_security_number = models.CharField(unique=True, max_length=33)
    name = models.CharField(unique=True, max_length=255)
    phone = models.CharField(unique=True, max_length=255)
    deceased = models.IntegerField(unique=True, null=True, blank=True)
    do_not_mail = models.IntegerField(null=True, blank=True)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()

    def distinguishing_column_names(self):
        return ['name', 'customer_number', 'social_security_number', 'phone']

Haben Sie eine Idee, warum genau das nicht funktioniert?

Bearbeiten: Hier ist McifModel :

from django.db import models
from django.db import connection, transaction

class McifModel(models.Model):
    class Meta:
        abstract = True

    def upsert(self):
        cursor = connection.cursor()
        cursor.execute(self.upsert_sql())
        transaction.commit_unless_managed()
        return self

    def value_list(self):
        return ','.join(map(lambda column_name: "'{c}'".format(c=getattr(self, column_name)), self.distinguishing_column_names()))

    def upsert_sql(self):
        column_names = ','.join(self.distinguishing_column_names())
        return "INSERT IGNORE INTO {t} ({c}) VALUES ({v})".format(t=self._meta.db_table, c=column_names, v=self.value_list())

    @classmethod
    def save_from_row(cls, row):
        object = cls()
        map(lambda column_name: setattr(object, column_name, row.value(object._meta.db_table, column_name)), object.distinguishing_column_names())
        return object.upsert()

Edit: Ich habe tarequehs Rat befolgt und den Inhalt der Caktus-Datei in mcif/utils.py . Ich setze auch TEST_RUNNER = 'mcif.utils.ManagedModelTestRunner' . Wenn ich die Konsole aufrufe, kann ich überprüfen, dass Customer wird nicht verwaltet:

>>> [m for m in get_models() if not m._meta.managed]
[<class 'mcif.models.customer.Customer'>]

Mein Test meldet jedoch immer noch, dass die Tabelle nicht existiert. Was übersehe ich?

Hier ist meine settings.py:

# Django settings for mcifdjango project.

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
    ('Jason Swett', 'jason.swett@gmail.com'),
)

MANAGERS = ADMINS

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'xxxxx',                # Or path to database file if using sqlite3.
        'USER': 'xxxxx',                       # Not used with sqlite3.
        'PASSWORD': 'xxxxx',           # Not used with sqlite3.
        'HOST': '',                           # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                           # Set to empty string for default. Not used with sqlite3.
    }
}

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = '#7+qm%hqfe+z8ul5@x_i&sqmu!n=4sa0&i0_#)m99*w$fbk3%#'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
#     'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
)

ROOT_URLCONF = 'mcifdjango.urls'

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.admin',
    'django_extensions',
    'mcif',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

TEST_RUNNER = 'mcif.utils.ManagedModelTestRunner'

import os
ROOTDIR = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    ROOTDIR + '/mcif/templates',
)

Bearbeiten 2:

Hier ist mein Customer Klasse jetzt:

from django.db import models
from django.db import connection, transaction
from mcif.models.mcif_model import McifModel

class Customer(McifModel):

    class Meta:
        db_table = u'customer'
        managed = False

    id = models.BigIntegerField(primary_key=True)
    customer_number = models.CharField(unique=True, max_length=255)
    social_security_number = models.CharField(unique=True, max_length=33)
    name = models.CharField(unique=True, max_length=255)
    phone = models.CharField(unique=True, max_length=255)
    deceased = models.IntegerField(unique=True, null=True, blank=True)
    do_not_mail = models.IntegerField(null=True, blank=True)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()

    def distinguishing_column_names(self):
        return ['name', 'customer_number', 'social_security_number', 'phone']

So sieht das Ergebnis aus, wenn ich den Test ausführe:

$ ./manage.py test mcif.CustomerUpsertTest
Creating test database 'default'...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_user_permissions
Creating table auth_user_groups
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Installing index for auth.Permission model
Installing index for auth.Group_permissions model
Installing index for auth.User_user_permissions model
Installing index for auth.User_groups model
Installing index for auth.Message model
Installing index for admin.LogEntry model
No fixtures found.
E
======================================================================
ERROR: test_upsert (mcif.tests.customer_upsert_test.CustomerUpsertTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jason/projects/mcifdjango/mcif/tests/customer_upsert_test.py", line 9, in test_upsert
    customer.upsert()
  File "/home/jason/projects/mcifdjango/mcif/models/mcif_model.py", line 11, in upsert
    cursor.execute(self.upsert_sql())
  File "/usr/lib/pymodules/python2.6/django/db/backends/mysql/base.py", line 86, in execute
    return self.cursor.execute(query, args)
  File "/usr/lib/pymodules/python2.6/MySQLdb/cursors.py", line 166, in execute
    self.errorhandler(self, exc, value)
  File "/usr/lib/pymodules/python2.6/MySQLdb/connections.py", line 35, in defaulterrorhandler
    raise errorclass, errorvalue
DatabaseError: (1146, "Table 'test_mcif_django.customer' doesn't exist")

----------------------------------------------------------------------
Ran 1 test in 3.724s

FAILED (errors=1)
Destroying test database 'default'...

25voto

tarequeh Punkte 1711

Da Sie eine Legacy-Datenbank verwenden, fügen Sie den Namen der Anwendung wahrscheinlich nicht zu INSTALLED_APPS hinzu. Wenn eine Anwendung nicht in INSTALLED_APPS enthalten ist, werden die Tabellen für die Modelle der Anwendungen nicht in syncdb erstellt. In der Produktionsumgebung funktioniert dies für Sie, da Sie bereits eine Tabelle haben, aber nicht in der Testumgebung.

Sie können eine der folgenden Möglichkeiten wählen:

  • Die Supermonkeypatch-Methode: Nehmen Sie app_name aus der Kundenklasse Meta heraus, legen Sie das Modell in einer models.py-Datei in einem Python-Modul namens mcif ab, und fügen Sie mcif zu INSTALLED_APPS hinzu - nur zum Testen

  • Die schönere Art: Erweitern Sie DjangoTestSuiteRunner und überschreiben Sie setup_test_environment um super aufzurufen und erstellen Sie dann Ihre Legacy-Tabelle manuell in der Test-DB.

  • Die schönste Art: Legen Sie Ihr Modell in das richtig benannte app-Modul. Entfernen Sie app_name aus dem Modell Meta und fügen Sie managed=False hinzu. docs . Fügen Sie den Namen der Anwendung in INSTALLED_APPS ein. Jetzt wird django keine Tabelle für dieses Modell erstellen. Dann verwenden Sie dies schöner Ausschnitt die die Mitarbeiter der Caktus-Gruppe zusammengestellt haben, um Ihre Tests durchzuführen.

Zum Wohl!

Bearbeiten - Wie man den überschriebenen DjangoTestSuiteRunner verwendet

Sie benötigen dafür mindestens Django 1.2.

Kopieren Sie den Code aus aquí . Fügen Sie es in die Datei utils.py innerhalb der mcif-Anwendung ein.

Fügen Sie Folgendes in settings.py hinzu/ändern Sie es:

TEST_RUNNER = 'mcif.utils.ManagedModelTestRunner'

Wenn Sie nun Tests durchführen, werden alle nicht verwalteten Tabellen nur für die Dauer des Tests als verwaltete Tabellen behandelt. Die Tabellen werden also vor der Ausführung der Tests erstellt.

Beachten Sie diesen Teil des Codes, denn hier geschieht der Zauber.

self.unmanaged_models = [m for m in get_models() if not m._meta.managed]
for m in self.unmanaged_models:
    m._meta.managed = True

2. Bearbeitung: Mögliche Fehler

Achten Sie auf folgende Punkte:

  • Der DB-Benutzer hat das Recht, Datenbanken und nicht nur Tabellen zu erstellen, da django versuchen wird, eine Testdatenbank zu erstellen
  • Die Testfälle erweitern django.test.TransactionTestCase, da Sie transaktionales Verhalten haben
  • Wenn keiner der oben genannten Punkte zutrifft, legen Sie eine pdb in die setup_test_environment von ManagedModelTestRunner, um sicherzustellen, dass der Code erreicht wird. Denn wenn dieser Code erreicht wird, sollte die Tabelle erstellt werden

3. Bearbeitung: Fehlersuche Ersetzen Sie in mcif.utils.ManagedModelTestRunner die Funktion setup_test_environment durch die folgende und lassen Sie mich wissen, ob sich die Ausgabe Ihres Tests ändert:

def setup_test_environment(self, *args, **kwargs):
    print "Loading ManagedModelTestRunner"
    from django.db.models.loading import get_models
    self.unmanaged_models = [m for m in get_models()
                             if not m._meta.managed]
    for m in self.unmanaged_models:
        print "Modifying model %s to be managed for testing" % m
        m._meta.managed = True
    super(ManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)

6voto

sandwichthecat Punkte 79

Die von tarequeh vorgestellten Lösungen haben bei mir funktioniert, nachdem ich DATABASE_ROUTERS überschrieben hatte.

Ich verwende Router, um Schreibzugriffe auf die veraltete Datenbank zu verhindern. Um dies zu umgehen, habe ich eine Datei test_settings mit dem folgenden Inhalt erstellt:

from settings import *

DEBUG = True

TEST_RUNNER = 'legacy.utils.ManagedModelTestRunner'

DATABASE_ROUTERS = []

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(HERE, 'test.db'),
    },
}

Wenn Sie dann Tests durchführen:

python manage.py test [app_name] --settings=test_settings

2voto

Jeff Bauer Punkte 13322

Es gibt nicht genügend Informationen, um Ihre erste Frage zu beantworten. Sobald Sie dieses Problem gelöst haben, werden Sie wahrscheinlich Folgendes installieren wollen django-erweiterungen aus folgendem Grund: Es hat eine unglaublich nützliche sqldiff der Sie informiert, wenn es eine Diskrepanz zwischen der Legacy-Datenbank und Ihrem Anwendungsmodell gibt.

1voto

Oliver Küchler Punkte 43

Hier ist eine aktuellere Lösung, die auch mit aktuellen Versionen von Django funktioniert (ich habe sie mit Django 3.2.11 getestet): https://medium.com/an-idea/testing-with-the-legacy-database-in-django-3be84786daba

Auch für den Fall, dass Sie die Django-Testdatenbank weiterhin mit den Daten Ihrer Legacy-Datenbank befüllen wollen: Einrichtungsgegenstände prüfen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X