Ticket #13960: filetransfers.diff
File filetransfers.diff, 18.5 KB (added by , 14 years ago) |
---|
-
django/conf/global_settings.py
diff -r 709838c4a6bb django/conf/global_settings.py
a b 257 257 # Default file storage mechanism that holds media. 258 258 DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' 259 259 260 FILE_BACKENDS = () 261 260 262 # Absolute path to the directory that holds media. 261 263 # Example: "/home/media/media.lawrence.com/" 262 264 MEDIA_ROOT = '' -
new file django/core/files/transfers.py
diff -r 709838c4a6bb django/core/files/transfers.py
- + 1 from django.conf import settings 2 from django.core.exceptions import ImproperlyConfigured 3 from django.core.files.storage import default_storage 4 from django.http import HttpResponse 5 from django.utils.encoding import smart_str 6 from django.utils.importlib import import_module 7 import mimetypes 8 9 class BaseFileBackend(object): 10 def __init__(self, model, fields): 11 self.model = model 12 if not hasattr(fields, '__iter__'): 13 fields = (fields,) 14 self.fields = fields 15 16 # Add a convenience accessor for serve, download_url, 17 # and get_storage_backend which only act on a single FileField 18 def _get_field(self): 19 if len(self.fields) != 1: 20 raise ValueError('Expected only a single FileField, got %d' % len(self.fields)) 21 return self.fields[0] 22 field = property(_get_field) 23 24 def prepare_upload(self, request, url): 25 return None 26 27 def serve(self, request, file, save_as=False, content_type=None): 28 return None 29 30 def download_url(self, file): 31 return None 32 33 def get_storage_backend(self): 34 return None 35 36 # Public API 37 def prepare_upload(model, fields, request, url=None): 38 if not url: 39 url = request.get_full_path() 40 for backend in _get_backends(model, fields): 41 result = backend.prepare_upload(request, url) 42 if result is not None: 43 return result 44 45 # By default we simply return the URL unmodified 46 return url, {} 47 48 def serve_file(model, fields, request, file, save_as=False, content_type=None): 49 filename = file.name.rsplit('/')[-1] 50 if save_as is True: 51 save_as = filename 52 if not content_type: 53 content_type = mimetypes.guess_type(filename)[0] 54 for backend in _get_backends(model, fields): 55 result = backend.serve(request, file, save_as=save_as, content_type=content_type) 56 if result is not None: 57 return result 58 59 return _default_serve_file(file, save_as, content_type) 60 61 def download_url(model, fields, file): 62 for backend in _get_backends(model, fields): 63 result = backend.download_url(file) 64 if result is not None: 65 return result 66 67 # By default we use MEDIA_URL 68 return settings.MEDIA_URL + file.name 69 70 def get_storage_backend(model, fields): 71 for backend in _get_backends(model, fields): 72 result = backend.get_storage_backend() 73 if result is not None: 74 return result 75 76 # By default there is no public download URL 77 return default_storage 78 79 # Internal utilities 80 class ChunkedFile(object): 81 def __init__(self, file): 82 self.file = file 83 84 def __iter__(self): 85 return self.file.chunks() 86 87 def _default_serve_file(file, save_as, content_type): 88 """ 89 Serves the file in chunks for efficiency reasons. 90 91 The transfer still goes through Django itself, so it's much worse than 92 using the web server, but at least it works with all configurations. 93 """ 94 response = HttpResponse(ChunkedFile(file), content_type=content_type) 95 if save_as: 96 response['Content-Disposition'] = smart_str(u'attachment; filename=%s' % save_as) 97 if file.size is not None: 98 response['Content-Length'] = file.size 99 return response 100 101 def _get_backends(model, fields): 102 return (_load_backend(name, model, fields) 103 for name in settings.FILE_BACKENDS) 104 105 def _load_backend(path, model, fields): 106 module_name, attr_name = path.rsplit('.', 1) 107 try: 108 mod = import_module(module_name) 109 except ImportError, e: 110 raise ImproperlyConfigured('Error importing file backend module %s: "%s"' % (module_name, e)) 111 except ValueError, e: 112 raise ImproperlyConfigured('Error importing file backend module. Is FILE_BACKENDS a correctly defined list or tuple?') 113 try: 114 backend = getattr(mod, attr_name) 115 except AttributeError: 116 raise ImproperlyConfigured('Module "%s" does not define a "%s" file backend' % (module_name, attr_name)) 117 return backend(model, fields) -
django/core/files/uploadhandler.py
diff -r 709838c4a6bb django/core/files/uploadhandler.py
a b 84 84 """ 85 85 pass 86 86 87 def new_file(self, field_name, file_name, content_type, content_length, charset=None):87 def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None): 88 88 """ 89 89 Signal that a new file has been started. 90 90 … … 96 96 self.content_type = content_type 97 97 self.content_length = content_length 98 98 self.charset = charset 99 if content_type_extra is None: 100 content_type_extra = {} 101 self.content_type_extra = content_type_extra 99 102 100 103 def receive_data_chunk(self, raw_data, start): 101 104 """ -
django/db/models/fields/files.py
diff -r 709838c4a6bb django/db/models/fields/files.py
a b 6 6 from django.conf import settings 7 7 from django.db.models.fields import Field 8 8 from django.core.files.base import File, ContentFile 9 from django.core.files.storage import default_storage10 9 from django.core.files.images import ImageFile, get_image_dimensions 10 from django.core.files.transfers import prepare_upload, serve_file, download_url, get_storage_backend 11 11 from django.core.files.uploadedfile import UploadedFile 12 12 from django.utils.functional import curry 13 13 from django.db.models import signals … … 139 139 # be restored later, by FileDescriptor below. 140 140 return {'name': self.name, 'closed': False, '_committed': True, '_file': None} 141 141 142 def serve(self, request, save_as=False, content_type=None): 143 self._require_file() 144 return serve_file(self.field.model, self.field, request, self, save_as=save_as, content_type=content_type) 145 146 def download_url(self): 147 self._require_file() 148 return download_url(self.field.model, self.field, self) 149 142 150 class FileDescriptor(object): 143 151 """ 144 152 The descriptor for the file attribute on the model instance. Returns a … … 156 164 157 165 def __get__(self, instance=None, owner=None): 158 166 if instance is None: 159 raise AttributeError( 160 "The '%s' attribute can only be accessed from %s instances." 161 % (self.field.name, owner.__name__)) 167 return self.field 162 168 163 169 # This is slightly complicated, so worth an explanation. 164 170 # instance.file`needs to ultimately return some instance of `File`, … … 224 230 if arg in kwargs: 225 231 raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__)) 226 232 227 self.storage = storage or default_storage233 self.storage = storage 228 234 self.upload_to = upload_to 229 235 if callable(upload_to): 230 236 self.generate_filename = upload_to … … 257 263 258 264 def contribute_to_class(self, cls, name): 259 265 super(FileField, self).contribute_to_class(cls, name) 266 if self.storage is None: 267 self.storage = get_storage_backend(self.model, self) 260 268 setattr(cls, self.name, self.descriptor_class(self)) 261 269 signals.post_delete.connect(self.delete_file, sender=cls) 262 270 … … 297 305 defaults.update(kwargs) 298 306 return super(FileField, self).formfield(**defaults) 299 307 308 def prepare_upload(self, request, url=None): 309 return prepare_upload(self.model, self, request, url=url) 310 300 311 class ImageFileDescriptor(FileDescriptor): 301 312 """ 302 313 Just like the FileDescriptor, but for ImageFields. The only difference is -
django/forms/models.py
diff -r 709838c4a6bb django/forms/models.py
a b 3 3 and database field objects. 4 4 """ 5 5 6 from django.core.files.transfers import prepare_upload 6 7 from django.db import connections 7 8 from django.utils.encoding import smart_unicode, force_unicode 8 9 from django.utils.datastructures import SortedDict … … 231 232 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 232 233 initial=None, error_class=ErrorList, label_suffix=':', 233 234 empty_permitted=False, instance=None): 235 self._file_upload_data = {} 236 234 237 opts = self._meta 235 238 if instance is None: 236 239 if opts.model is None: … … 372 375 373 376 save.alters_data = True 374 377 378 def prepare_upload(self, request, url=None): 379 from django.db import models 380 for name in self._file_upload_data: 381 del self.fields[name] 382 383 file_fields = [] 384 for name in self.fields: 385 try: 386 field = self._meta.model._meta.get_field(name) 387 if isinstance(field, models.FileField): 388 file_fields.append(field) 389 except models.FieldDoesNotExist: 390 pass 391 392 result = prepare_upload(self._meta.model, file_fields, request, url=url) 393 self.upload_url, self._file_upload_data = result 394 395 for name, value in self._file_upload_data.items(): 396 self.fields[name] = Field(initial=value, required=False, widget=HiddenInput) 397 375 398 class ModelForm(BaseModelForm): 376 399 __metaclass__ = ModelFormMetaclass 377 400 -
django/http/multipartparser.py
diff -r 709838c4a6bb django/http/multipartparser.py
a b 169 169 file_name = self.IE_sanitize(unescape_entities(file_name)) 170 170 171 171 content_type = meta_data.get('content-type', ('',))[0].strip() 172 content_type_extra = meta_data.get('content-type', (0,{}))[1] 172 173 try: 173 charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)174 charset = content_type_extra.get('charset', None) 174 175 except: 175 176 charset = None 176 177 … … 185 186 try: 186 187 handler.new_file(field_name, file_name, 187 188 content_type, content_length, 188 charset )189 charset, content_type_extra) 189 190 except StopFutureHandlers: 190 191 break 191 192 -
tests/modeltests/files/models.py
diff -r 709838c4a6bb tests/modeltests/files/models.py
a b 34 34 __test__ = {'API_TESTS':""" 35 35 # Attempting to access a FileField from the class raises a descriptive error 36 36 >>> Storage.normal 37 Traceback (most recent call last): 38 ... 39 AttributeError: The 'normal' attribute can only be accessed from Storage instances. 37 <django.db.models.fields.files.FileField object at ...> 40 38 41 39 # An object without a file has limited functionality. 42 40 -
new file tests/regressiontests/file_uploads/backends.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/backends.py
- + 1 from django.conf import settings 2 from django.core.files.transfers import BaseFileBackend 3 from django.http import HttpResponseRedirect 4 import models 5 6 class TestFileBackend(BaseFileBackend): 7 def prepare_upload(self, request, url, *args, **kwargs): 8 if self.model.__module__ == models.__name__: 9 return '/redirect' + url, {'x-extra': 'abcd', 'x-data': 'efg'} 10 11 def serve(self, request, file, *args, **kwargs): 12 if self.model.__module__ == models.__name__ and not file.name.endswith('pass'): 13 return HttpResponseRedirect('/download/') 14 15 def download_url(self, file): 16 if self.model.__module__ == models.__name__ and not file.name.endswith('pass'): 17 return '/public/' 18 19 def get_storage_backend(self, *args, **kwargs): 20 if self.model.__module__ == models.__name__: 21 return models.temp_storage -
new file tests/regressiontests/file_uploads/forms.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/forms.py
- + 1 from django import forms 2 from models import FileModel 3 4 class FileForm(forms.ModelForm): 5 class Meta: 6 model = FileModel -
tests/regressiontests/file_uploads/models.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/models.py
a b 1 1 import tempfile 2 2 import os 3 from django.conf import settings 3 4 from django.db import models 4 5 from django.core.files.storage import FileSystemStorage 5 6 6 7 temp_storage = FileSystemStorage(tempfile.mkdtemp()) 7 8 UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload') 9 settings.FILE_BACKENDS = ('%s.backends.TestFileBackend' % __name__.rsplit('.', 1)[0],) + \ 10 settings.FILE_BACKENDS 8 11 9 12 class FileModel(models.Model): 10 testfile = models.FileField( storage=temp_storage,upload_to='test_upload')13 testfile = models.FileField(upload_to='test_upload') -
tests/regressiontests/file_uploads/tests.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/tests.py
a b 5 5 import unittest 6 6 from StringIO import StringIO 7 7 8 from django.conf import settings 8 9 from django.core.files import temp as tempfile 9 10 from django.core.files.uploadedfile import SimpleUploadedFile 10 11 from django.test import TestCase, client … … 17 18 18 19 UNICODE_FILENAME = u'test-0123456789_中文_Orléans.jpg' 19 20 21 class FileBackendTests(TestCase): 22 def setUp(self): 23 if not os.path.isdir(temp_storage.location): 24 os.makedirs(temp_storage.location) 25 if os.path.isdir(UPLOAD_TO): 26 shutil.rmtree(UPLOAD_TO) 27 28 def tearDown(self): 29 shutil.rmtree(temp_storage.location) 30 31 def test_prepare_upload(self): 32 response = self.client.get('/file_uploads/prepare_upload/') 33 self.assertEqual(response.status_code, 200) 34 data = simplejson.loads(response.content) 35 self.assertEqual(data, [ 36 '/redirect/file_uploads/prepare_upload/', [ 37 '<input type="file" name="testfile" id="id_testfile" />', 38 '<input type="hidden" name="x-data" value="efg" id="id_x-data" />', 39 '<input type="hidden" name="x-extra" value="abcd" id="id_x-extra" />', 40 ] 41 ]) 42 43 def test_serve_file(self): 44 obj = FileModel() 45 obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', 'x')) 46 obj.save() 47 response = self.client.get('/file_uploads/serve/') 48 self.assertEqual(response.status_code, 302) 49 self.assertEqual(response['Location'], 'http://testserver/download/') 50 51 def test_serve_file_fallback(self): 52 obj = FileModel() 53 obj.testfile.save('foo.pass', SimpleUploadedFile('foo.pass', 'x')) 54 obj.save() 55 response = self.client.get('/file_uploads/serve/') 56 self.assertEqual(response.status_code, 200) 57 58 def test_download_url(self): 59 obj = FileModel() 60 obj.testfile.save('foo2.txt', SimpleUploadedFile('foo2.txt', 'x')) 61 obj.save() 62 url = obj.testfile.download_url() 63 self.assertEqual(url, '/public/') 64 65 def test_download_url_fallback(self): 66 obj = FileModel() 67 obj.testfile.save('foo2.pass', SimpleUploadedFile('foo2.pass', 'x')) 68 obj.save() 69 url = obj.testfile.download_url() 70 self.assertEqual(url, settings.MEDIA_URL + obj.testfile.name) 71 20 72 class FileUploadTests(TestCase): 21 73 def test_simple_upload(self): 22 74 post_data = { … … 122 174 response = self.client.request(**r) 123 175 124 176 # The filenames should have been sanitized by the time it got to the view. 125 rec ieved = simplejson.loads(response.content)177 received = simplejson.loads(response.content) 126 178 for i, name in enumerate(scary_file_names): 127 got = rec ieved["file%s" % i]179 got = received["file%s" % i] 128 180 self.assertEqual(got, "hax0rd.txt") 129 181 130 182 def test_filename_overflow(self): -
tests/regressiontests/file_uploads/urls.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/urls.py
a b 10 10 (r'^quota/broken/$', views.file_upload_quota_broken), 11 11 (r'^getlist_count/$', views.file_upload_getlist_count), 12 12 (r'^upload_errors/$', views.file_upload_errors), 13 (r'^prepare_upload/$', views.file_prepare_upload), 14 (r'^serve/$', views.file_serve), 13 15 ) -
tests/regressiontests/file_uploads/views.py
diff -r 709838c4a6bb tests/regressiontests/file_uploads/views.py
a b 2 2 from django.core.files.uploadedfile import UploadedFile 3 3 from django.http import HttpResponse, HttpResponseServerError 4 4 from django.utils import simplejson 5 from forms import FileForm 5 6 from models import FileModel, UPLOAD_TO 6 7 from uploadhandler import QuotaUploadHandler, ErroringUploadHandler 7 8 from django.utils.hashcompat import sha_constructor … … 112 113 def file_upload_errors(request): 113 114 request.upload_handlers.insert(0, ErroringUploadHandler()) 114 115 return file_upload_echo(request) 116 117 def file_prepare_upload(request): 118 form = FileForm() 119 form.prepare_upload(request) 120 return HttpResponse(simplejson.dumps([form.upload_url, list(map(unicode, form))])) 121 122 def file_serve(request): 123 return FileModel.objects.all()[0].testfile.serve(request)