Fixed #14300 -- Fixed initial SQL location if models is a package.

Thanks al_the_x for the report and fheinz for the draft patch.
This commit is contained in:
Tim Graham 2013-07-16 09:10:04 -04:00
parent c928725b93
commit 31c13a99bb
9 changed files with 64 additions and 9 deletions

View File

@ -233,7 +233,7 @@ class Command(BaseCommand):
""" """
dirs = [] dirs = []
for path in get_app_paths(): for path in get_app_paths():
d = os.path.join(os.path.dirname(path), 'fixtures') d = os.path.join(path, 'fixtures')
if os.path.isdir(d): if os.path.isdir(d):
dirs.append(d) dirs.append(d)
dirs.extend(list(settings.FIXTURE_DIRS)) dirs.extend(list(settings.FIXTURE_DIRS))

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import codecs import codecs
import os import os
import re import re
import warnings
from django.conf import settings from django.conf import settings
from django.core.management.base import CommandError from django.core.management.base import CommandError
@ -168,7 +169,18 @@ def _split_statements(content):
def custom_sql_for_model(model, style, connection): def custom_sql_for_model(model, style, connection):
opts = model._meta opts = model._meta
app_dir = os.path.normpath(os.path.join(os.path.dirname(upath(models.get_app(model._meta.app_label).__file__)), 'sql')) app_dirs = []
app_dir = models.get_app_path(model._meta.app_label)
app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql')))
# Deprecated location -- remove in Django 1.9
old_app_dir = os.path.normpath(os.path.join(app_dir, 'models/sql'))
if os.path.exists(old_app_dir):
warnings.warn("Custom SQL location '<app_label>/models/sql' is "
"deprecated, use '<app_label>/sql' instead.",
PendingDeprecationWarning)
app_dirs.append(old_app_dir)
output = [] output = []
# Post-creation SQL should come before any initial SQL data is loaded. # Post-creation SQL should come before any initial SQL data is loaded.
@ -181,8 +193,10 @@ def custom_sql_for_model(model, style, connection):
# Find custom SQL, if it's available. # Find custom SQL, if it's available.
backend_name = connection.settings_dict['ENGINE'].split('.')[-1] backend_name = connection.settings_dict['ENGINE'].split('.')[-1]
sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.model_name, backend_name)), sql_files = []
os.path.join(app_dir, "%s.sql" % opts.model_name)] for app_dir in app_dirs:
sql_files.append(os.path.join(app_dir, "%s.%s.sql" % (opts.model_name, backend_name)))
sql_files.append(os.path.join(app_dir, "%s.sql" % opts.model_name))
for sql_file in sql_files: for sql_file in sql_files:
if os.path.exists(sql_file): if os.path.exists(sql_file):
with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp: with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp:

View File

@ -1,7 +1,7 @@
from functools import wraps from functools import wraps
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.db.models.loading import get_apps, get_app_paths, get_app, get_models, get_model, register_models, UnavailableApp from django.db.models.loading import get_apps, get_app_path, get_app_paths, get_app, get_models, get_model, register_models, UnavailableApp
from django.db.models.query import Q from django.db.models.query import Q
from django.db.models.expressions import F from django.db.models.expressions import F
from django.db.models.manager import Manager from django.db.models.manager import Manager

View File

@ -154,6 +154,16 @@ class AppCache(object):
return [elt[0] for elt in apps] return [elt[0] for elt in apps]
def _get_app_path(self, app):
if hasattr(app, '__path__'): # models/__init__.py package
app_path = app.__path__[0]
else: # models.py module
app_path = app.__file__
return os.path.dirname(upath(app_path))
def get_app_path(self, app_label):
return self._get_app_path(self.get_app(app_label))
def get_app_paths(self): def get_app_paths(self):
""" """
Returns a list of paths to all installed apps. Returns a list of paths to all installed apps.
@ -165,10 +175,7 @@ class AppCache(object):
app_paths = [] app_paths = []
for app in self.get_apps(): for app in self.get_apps():
if hasattr(app, '__path__'): # models/__init__.py package app_paths.append(self._get_app_path(app))
app_paths.extend([upath(path) for path in app.__path__])
else: # models.py module
app_paths.append(upath(app.__file__))
return app_paths return app_paths
def get_app(self, app_label, emptyOK=False): def get_app(self, app_label, emptyOK=False):
@ -321,6 +328,7 @@ cache = AppCache()
# These methods were always module level, so are kept that way for backwards # These methods were always module level, so are kept that way for backwards
# compatibility. # compatibility.
get_apps = cache.get_apps get_apps = cache.get_apps
get_app_path = cache.get_app_path
get_app_paths = cache.get_app_paths get_app_paths = cache.get_app_paths
get_app = cache.get_app get_app = cache.get_app
get_app_errors = cache.get_app_errors get_app_errors = cache.get_app_errors

View File

@ -414,6 +414,10 @@ these changes.
* ``django.utils.unittest`` will be removed. * ``django.utils.unittest`` will be removed.
* If models are organized in a package, Django will no longer look for
:ref:`initial SQL data<initial-sql>` in ``myapp/models/sql/``. Move your
custom SQL files to ``myapp/sql/``.
2.0 2.0
--- ---

View File

@ -116,3 +116,12 @@ on all Python versions. Since ``unittest2`` became the standard library's
:mod:`unittest` module in Python 2.7, and Django 1.7 drops support for older :mod:`unittest` module in Python 2.7, and Django 1.7 drops support for older
Python versions, this module isn't useful anymore. It has been deprecated. Use Python versions, this module isn't useful anymore. It has been deprecated. Use
:mod:`unittest` instead. :mod:`unittest` instead.
Custom SQL location for models package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, if models were organized in a package (``myapp/models/``) rather
than simply ``myapp/models.py``, Django would look for :ref:`initial SQL data
<initial-sql>` in ``myapp/models/sql/``. This bug has been fixed so that Django
will search ``myapp/sql/`` as documented. The old location will continue to
work until Django 1.9.

View File

@ -0,0 +1,2 @@
-- Deprecated search path for custom SQL -- remove in Django 1.9
INSERT INTO fixtures_model_package_book (name) VALUES ('My Deprecated Book');

View File

@ -0,0 +1 @@
INSERT INTO fixtures_model_package_book (name) VALUES ('My Book');

View File

@ -5,6 +5,7 @@ import warnings
from django.core import management from django.core import management
from django.db import transaction from django.db import transaction
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.utils.six import StringIO
from .models import Article, Book from .models import Article, Book
@ -110,3 +111,19 @@ class FixtureTestCase(TestCase):
], ],
lambda a: a.headline, lambda a: a.headline,
) )
class InitialSQLTests(TestCase):
def test_custom_sql(self):
"""
#14300 -- Verify that custom_sql_for_model searches `app/sql` and not
`app/models/sql` (the old location will work until Django 1.9)
"""
out = StringIO()
management.call_command("sqlcustom", "fixtures_model_package", stdout=out)
output = out.getvalue()
self.assertTrue("INSERT INTO fixtures_model_package_book (name) "
"VALUES ('My Book')" in output)
# value from deprecated search path models/sql (remove in Django 1.9)
self.assertTrue("Deprecated Book" in output)