2008-08-27 22:53:02 +02:00
|
|
|
# coding: utf-8
|
2008-08-08 22:59:02 +02:00
|
|
|
"""
|
|
|
|
Tests for the file storage mechanism
|
|
|
|
|
|
|
|
>>> import tempfile
|
|
|
|
>>> from django.core.files.storage import FileSystemStorage
|
|
|
|
>>> from django.core.files.base import ContentFile
|
|
|
|
|
2008-08-15 15:16:16 +02:00
|
|
|
# Set up a unique temporary directory
|
|
|
|
>>> import os
|
|
|
|
>>> temp_dir = tempfile.mktemp()
|
|
|
|
>>> os.makedirs(temp_dir)
|
|
|
|
|
|
|
|
>>> temp_storage = FileSystemStorage(location=temp_dir)
|
2008-08-08 22:59:02 +02:00
|
|
|
|
|
|
|
# Standard file access options are available, and work as expected.
|
|
|
|
|
|
|
|
>>> temp_storage.exists('storage_test')
|
|
|
|
False
|
|
|
|
>>> file = temp_storage.open('storage_test', 'w')
|
|
|
|
>>> file.write('storage contents')
|
|
|
|
>>> file.close()
|
|
|
|
|
|
|
|
>>> temp_storage.exists('storage_test')
|
|
|
|
True
|
|
|
|
>>> file = temp_storage.open('storage_test', 'r')
|
|
|
|
>>> file.read()
|
|
|
|
'storage contents'
|
|
|
|
>>> file.close()
|
|
|
|
|
|
|
|
>>> temp_storage.delete('storage_test')
|
|
|
|
>>> temp_storage.exists('storage_test')
|
|
|
|
False
|
|
|
|
|
|
|
|
# Files can only be accessed if they're below the specified location.
|
|
|
|
|
|
|
|
>>> temp_storage.exists('..')
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
SuspiciousOperation: Attempted access to '..' denied.
|
|
|
|
>>> temp_storage.open('/etc/passwd')
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
SuspiciousOperation: Attempted access to '/etc/passwd' denied.
|
|
|
|
|
|
|
|
# Custom storage systems can be created to customize behavior
|
|
|
|
|
|
|
|
>>> class CustomStorage(FileSystemStorage):
|
|
|
|
... def get_available_name(self, name):
|
|
|
|
... # Append numbers to duplicate files rather than underscores, like Trac
|
|
|
|
...
|
|
|
|
... parts = name.split('.')
|
|
|
|
... basename, ext = parts[0], parts[1:]
|
|
|
|
... number = 2
|
|
|
|
...
|
|
|
|
... while self.exists(name):
|
|
|
|
... name = '.'.join([basename, str(number)] + ext)
|
|
|
|
... number += 1
|
|
|
|
...
|
|
|
|
... return name
|
2008-08-15 15:16:16 +02:00
|
|
|
>>> custom_storage = CustomStorage(temp_dir)
|
2008-08-08 22:59:02 +02:00
|
|
|
|
|
|
|
>>> first = custom_storage.save('custom_storage', ContentFile('custom contents'))
|
|
|
|
>>> first
|
|
|
|
u'custom_storage'
|
|
|
|
>>> second = custom_storage.save('custom_storage', ContentFile('more contents'))
|
|
|
|
>>> second
|
|
|
|
u'custom_storage.2'
|
|
|
|
|
|
|
|
>>> custom_storage.delete(first)
|
|
|
|
>>> custom_storage.delete(second)
|
2008-08-15 15:16:16 +02:00
|
|
|
|
|
|
|
# Cleanup the temp dir
|
|
|
|
>>> os.rmdir(temp_dir)
|
|
|
|
|
2008-08-27 22:53:02 +02:00
|
|
|
|
|
|
|
# Regression test for #8156: files with unicode names I can't quite figure out the
|
|
|
|
# encoding situation between doctest and this file, but the actual repr doesn't
|
|
|
|
# matter; it just shouldn't return a unicode object.
|
|
|
|
>>> from django.core.files.uploadedfile import UploadedFile
|
|
|
|
>>> uf = UploadedFile(name=u'¿Cómo?',content_type='text')
|
|
|
|
>>> uf.__repr__()
|
|
|
|
'<UploadedFile: ... (text)>'
|
2008-08-08 22:59:02 +02:00
|
|
|
"""
|
2008-08-11 18:51:18 +02:00
|
|
|
|
|
|
|
# Tests for a race condition on file saving (#4948).
|
2009-05-08 07:50:31 +02:00
|
|
|
# This is written in such a way that it'll always pass on platforms
|
2008-08-11 18:51:18 +02:00
|
|
|
# without threading.
|
2008-08-28 00:21:14 +02:00
|
|
|
import os
|
2008-08-11 18:51:18 +02:00
|
|
|
import time
|
2008-10-11 00:13:16 +02:00
|
|
|
import shutil
|
2009-05-08 07:50:31 +02:00
|
|
|
import sys
|
2008-10-11 00:13:16 +02:00
|
|
|
import tempfile
|
2008-08-11 18:51:18 +02:00
|
|
|
from unittest import TestCase
|
2008-08-28 00:21:14 +02:00
|
|
|
from django.conf import settings
|
2008-08-11 18:51:18 +02:00
|
|
|
from django.core.files.base import ContentFile
|
2008-10-11 00:13:16 +02:00
|
|
|
from django.core.files.storage import FileSystemStorage
|
2008-08-11 18:51:18 +02:00
|
|
|
try:
|
|
|
|
import threading
|
|
|
|
except ImportError:
|
|
|
|
import dummy_threading as threading
|
|
|
|
|
|
|
|
class SlowFile(ContentFile):
|
|
|
|
def chunks(self):
|
|
|
|
time.sleep(1)
|
|
|
|
return super(ContentFile, self).chunks()
|
|
|
|
|
|
|
|
class FileSaveRaceConditionTest(TestCase):
|
|
|
|
def setUp(self):
|
2008-10-11 00:13:16 +02:00
|
|
|
self.storage_dir = tempfile.mkdtemp()
|
|
|
|
self.storage = FileSystemStorage(self.storage_dir)
|
2008-08-11 18:51:18 +02:00
|
|
|
self.thread = threading.Thread(target=self.save_file, args=['conflict'])
|
2009-05-08 07:50:31 +02:00
|
|
|
|
2008-10-11 00:13:16 +02:00
|
|
|
def tearDown(self):
|
|
|
|
shutil.rmtree(self.storage_dir)
|
2009-05-08 07:50:31 +02:00
|
|
|
|
2008-08-11 18:51:18 +02:00
|
|
|
def save_file(self, name):
|
2008-10-11 00:13:16 +02:00
|
|
|
name = self.storage.save(name, SlowFile("Data"))
|
2009-05-08 07:50:31 +02:00
|
|
|
|
2008-08-11 18:51:18 +02:00
|
|
|
def test_race_condition(self):
|
|
|
|
self.thread.start()
|
|
|
|
name = self.save_file('conflict')
|
|
|
|
self.thread.join()
|
2008-10-11 00:13:16 +02:00
|
|
|
self.assert_(self.storage.exists('conflict'))
|
|
|
|
self.assert_(self.storage.exists('conflict_'))
|
|
|
|
self.storage.delete('conflict')
|
|
|
|
self.storage.delete('conflict_')
|
2008-08-11 18:51:18 +02:00
|
|
|
|
2008-08-28 00:21:14 +02:00
|
|
|
class FileStoragePermissions(TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.old_perms = settings.FILE_UPLOAD_PERMISSIONS
|
|
|
|
settings.FILE_UPLOAD_PERMISSIONS = 0666
|
2008-10-11 00:13:16 +02:00
|
|
|
self.storage_dir = tempfile.mkdtemp()
|
|
|
|
self.storage = FileSystemStorage(self.storage_dir)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
settings.FILE_UPLOAD_PERMISSIONS = self.old_perms
|
|
|
|
shutil.rmtree(self.storage_dir)
|
|
|
|
|
2008-08-28 00:21:14 +02:00
|
|
|
def test_file_upload_permissions(self):
|
2008-10-11 00:13:16 +02:00
|
|
|
name = self.storage.save("the_file", ContentFile("data"))
|
|
|
|
actual_mode = os.stat(self.storage.path(name))[0] & 0777
|
2008-08-28 00:21:14 +02:00
|
|
|
self.assertEqual(actual_mode, 0666)
|
2008-10-11 00:13:16 +02:00
|
|
|
|
2009-05-08 07:50:31 +02:00
|
|
|
|
|
|
|
class FileStoragePathParsing(TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.storage_dir = tempfile.mkdtemp()
|
|
|
|
self.storage = FileSystemStorage(self.storage_dir)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
shutil.rmtree(self.storage_dir)
|
|
|
|
|
|
|
|
def test_directory_with_dot(self):
|
|
|
|
"""Regression test for #9610.
|
|
|
|
|
|
|
|
If the directory name contains a dot and the file name doesn't, make
|
|
|
|
sure we still mangle the file name instead of the directory name.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.storage.save('dotted.path/test', ContentFile("1"))
|
|
|
|
self.storage.save('dotted.path/test', ContentFile("2"))
|
|
|
|
|
|
|
|
self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_')))
|
|
|
|
|
|
|
|
def test_first_character_dot(self):
|
|
|
|
"""
|
|
|
|
File names with a dot as their first character don't have an extension,
|
|
|
|
and the underscore should get added to the end.
|
|
|
|
"""
|
|
|
|
self.storage.save('dotted.path/.test', ContentFile("1"))
|
|
|
|
self.storage.save('dotted.path/.test', ContentFile("2"))
|
|
|
|
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
|
|
|
|
# Before 2.6, a leading dot was treated as an extension, and so
|
|
|
|
# underscore gets added to beginning instead of end.
|
|
|
|
if sys.version_info < (2, 6):
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_.test')))
|
|
|
|
else:
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_')))
|