diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 32a676d846d..af108e08e11 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -21,6 +21,7 @@ from django.utils import lru_cache from django.utils._os import upath from django.utils.encoding import force_text from django.utils.functional import cached_property +from django.utils.glob import glob_escape try: import bz2 @@ -209,7 +210,8 @@ class Command(BaseCommand): if self.verbosity >= 2: self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir)) fixture_files_in_dir = [] - for candidate in glob.iglob(os.path.join(fixture_dir, fixture_name + '*')): + path = os.path.join(fixture_dir, fixture_name) + for candidate in glob.iglob(glob_escape(path) + '*'): if os.path.basename(candidate) in targets: # Save the fixture_dir and fixture_name for future error messages. fixture_files_in_dir.append((candidate, fixture_dir, fixture_name)) diff --git a/django/utils/glob.py b/django/utils/glob.py new file mode 100644 index 00000000000..a3138b09dcc --- /dev/null +++ b/django/utils/glob.py @@ -0,0 +1,36 @@ +from __future__ import unicode_literals + +import os.path +import re + +from django.utils import six + +# backport of Python 3.4's glob.escape + +try: + from glob import escape as glob_escape +except ImportError: + _magic_check = re.compile('([*?[])') + + if six.PY3: + _magic_check_bytes = re.compile(b'([*?[])') + + def glob_escape(pathname): + """ + Escape all special characters. + """ + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = _magic_check_bytes.sub(br'[\1]', pathname) + else: + pathname = _magic_check.sub(r'[\1]', pathname) + return drive + pathname + + else: + def glob_escape(pathname): + """ + Escape all special characters. + """ + drive, pathname = os.path.splitdrive(pathname) + pathname = _magic_check.sub(r'[\1]', pathname) + return drive + pathname diff --git a/tests/fixtures/fixtures/fixture?with[special]chars*.json b/tests/fixtures/fixtures/fixture?with[special]chars*.json new file mode 100644 index 00000000000..b6b7ad2a7ce --- /dev/null +++ b/tests/fixtures/fixtures/fixture?with[special]chars*.json @@ -0,0 +1,10 @@ +[ + { + "pk": "1", + "model": "fixtures.article", + "fields": { + "headline": "How To Deal With Special Characters", + "pub_date": "2006-06-16 12:00:00" + } + } +] diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index 544b70473ba..d87f1942ff0 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -224,6 +224,10 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase): "Unknown model in excludes: fixtures.FooModel"): self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel']) + def test_load_fixture_with_special_characters(self): + management.call_command('loaddata', 'fixture?with[special]chars*', verbosity=0) + self.assertQuerysetEqual(Article.objects.all(), ['']) + def test_dumpdata_with_filtering_manager(self): spy1 = Spy.objects.create(name='Paul') spy2 = Spy.objects.create(name='Alex', cover_blown=True) diff --git a/tests/utils_tests/test_glob.py b/tests/utils_tests/test_glob.py new file mode 100644 index 00000000000..7e72815ef85 --- /dev/null +++ b/tests/utils_tests/test_glob.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +from django.test import SimpleTestCase +from django.utils.glob import glob_escape + + +class TestUtilsGlob(SimpleTestCase): + def test_glob_escape(self): + filename = '/my/file?/name[with special chars*' + expected = '/my/file[?]/name[[]with special chars[*]' + filename_b = b'/my/file?/name[with special chars*' + expected_b = b'/my/file[?]/name[[]with special chars[*]' + + self.assertEqual(glob_escape(filename), expected) + self.assertEqual(glob_escape(filename_b), expected_b)