diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 54eaab0169..563a6ac161 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -6,6 +6,7 @@ from optparse import make_option from django.conf import settings from django.core.files.storage import get_storage_class from django.core.management.base import CommandError, NoArgsCommand +from django.utils.encoding import smart_str from django.contrib.staticfiles import finders @@ -64,7 +65,7 @@ class Command(NoArgsCommand): # Warn before doing anything more. if options.get('interactive'): - confirm = raw_input(""" + confirm = raw_input(u""" You have requested to collect static files at the destination location as specified in your settings file ('%s'). @@ -90,17 +91,18 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) actual_count = len(self.copied_files) + len(self.symlinked_files) unmodified_count = len(self.unmodified_files) if self.verbosity >= 1: - self.stdout.write("\n%s static file%s %s to '%s'%s.\n" + self.stdout.write(smart_str(u"\n%s static file%s %s to '%s'%s.\n" % (actual_count, actual_count != 1 and 's' or '', symlink and 'symlinked' or 'copied', settings.STATIC_ROOT, unmodified_count and ' (%s unmodified)' - % unmodified_count or '')) + % unmodified_count or ''))) def log(self, msg, level=2): """ Small log helper """ + msg = smart_str(msg) if not msg.endswith("\n"): msg += "\n" if self.verbosity >= level: @@ -135,13 +137,13 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) (not symlink and full_path and os.path.islink(full_path))): if prefixed_path not in self.unmodified_files: self.unmodified_files.append(prefixed_path) - self.log("Skipping '%s' (not modified)" % path) + self.log(u"Skipping '%s' (not modified)" % path) return False # Then delete the existing file if really needed if options['dry_run']: - self.log("Pretending to delete '%s'" % path) + self.log(u"Pretending to delete '%s'" % path) else: - self.log("Deleting '%s'" % path) + self.log(u"Deleting '%s'" % path) self.storage.delete(prefixed_path) return True @@ -151,7 +153,7 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) """ # Skip this file if it was already copied earlier if prefixed_path in self.symlinked_files: - return self.log("Skipping '%s' (already linked earlier)" % path) + return self.log(u"Skipping '%s' (already linked earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage, **options): return @@ -159,9 +161,9 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) source_path = source_storage.path(path) # Finally link the file if options['dry_run']: - self.log("Pretending to link '%s'" % source_path, level=1) + self.log(u"Pretending to link '%s'" % source_path, level=1) else: - self.log("Linking '%s'" % source_path, level=1) + self.log(u"Linking '%s'" % source_path, level=1) full_path = self.storage.path(prefixed_path) try: os.makedirs(os.path.dirname(full_path)) @@ -177,7 +179,7 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) """ # Skip this file if it was already copied earlier if prefixed_path in self.copied_files: - return self.log("Skipping '%s' (already copied earlier)" % path) + return self.log(u"Skipping '%s' (already copied earlier)" % path) # Delete the target file if needed or break if not self.delete_file(path, prefixed_path, source_storage, **options): return @@ -185,9 +187,9 @@ Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT) source_path = source_storage.path(path) # Finally start copying if options['dry_run']: - self.log("Pretending to copy '%s'" % source_path, level=1) + self.log(u"Pretending to copy '%s'" % source_path, level=1) else: - self.log("Copying '%s'" % source_path, level=1) + self.log(u"Copying '%s'" % source_path, level=1) if self.local: full_path = self.storage.path(prefixed_path) try: diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index 4d8b0c05b4..bcf0c2f055 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -1,6 +1,7 @@ import os from optparse import make_option from django.core.management.base import LabelCommand +from django.utils.encoding import smart_str, smart_unicode from django.contrib.staticfiles import finders @@ -16,11 +17,15 @@ class Command(LabelCommand): def handle_label(self, path, **options): verbosity = int(options.get('verbosity', 1)) result = finders.find(path, all=options['all']) + path = smart_unicode(path) if result: if not isinstance(result, (list, tuple)): result = [result] - output = '\n '.join((os.path.realpath(path) for path in result)) - self.stdout.write("Found %r here:\n %s\n" % (path, output)) + output = u'\n '.join( + (smart_unicode(os.path.realpath(path)) for path in result)) + self.stdout.write( + smart_str(u"Found '%s' here:\n %s\n" % (path, output))) else: if verbosity >= 1: - self.stdout.write("No matching file found for %r.\n" % path) + self.stderr.write( + smart_str("No matching file found for '%s'.\n" % path)) diff --git a/tests/regressiontests/staticfiles_tests/apps/test/static/test/speçial.txt b/tests/regressiontests/staticfiles_tests/apps/test/static/test/speçial.txt new file mode 100644 index 0000000000..5da2e16831 --- /dev/null +++ b/tests/regressiontests/staticfiles_tests/apps/test/static/test/speçial.txt @@ -0,0 +1 @@ +speçial in the app dir \ No newline at end of file diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 0ceb11bafd..0e73e48480 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- +import codecs import os import posixpath import shutil @@ -11,6 +13,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage from django.core.management import call_command from django.test import TestCase +from django.utils.encoding import smart_unicode from django.utils._os import rmtree_errorhandler @@ -72,8 +75,8 @@ class StaticFilesTestCase(TestCase): settings.INSTALLED_APPS = self.old_installed_apps def assertFileContains(self, filepath, text): - self.assertTrue(text in self._get_file(filepath), - "'%s' not in '%s'" % (text, filepath)) + self.assertTrue(text in self._get_file(smart_unicode(filepath)), + u"'%s' not in '%s'" % (text, filepath)) def assertFileNotFound(self, filepath): self.assertRaises(IOError, self._get_file, filepath) @@ -108,7 +111,7 @@ class BuildStaticTestCase(StaticFilesTestCase): def _get_file(self, filepath): assert filepath, 'filepath is empty.' filepath = os.path.join(settings.STATIC_ROOT, filepath) - f = open(filepath) + f = codecs.open(filepath, "r", "utf-8") try: return f.read() finally: @@ -140,10 +143,16 @@ class TestDefaults(object): def test_app_files(self): """ - Can find a file in an app media/ directory. + Can find a file in an app static/ directory. """ self.assertFileContains('test/file1.txt', 'file1 in the app dir') + def test_nonascii_filenames(self): + """ + Can find a file with non-ASCII character in an app static/ directory. + """ + self.assertFileContains(u'test/speçial.txt', u'speçial in the app dir') + class TestFindStatic(BuildStaticTestCase, TestDefaults): """ @@ -156,7 +165,7 @@ class TestFindStatic(BuildStaticTestCase, TestDefaults): call_command('findstatic', filepath, all=False, verbosity='0') sys.stdout.seek(0) lines = [l.strip() for l in sys.stdout.readlines()] - contents = open(lines[1].strip()).read() + contents = codecs.open(lines[1].strip(), "r", "utf-8").read() finally: sys.stdout = _stdout return contents