Ticket #13960: filetransfers.2.diff
File filetransfers.2.diff, 17.3 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 isinstance(fields, (tuple, list)): 13 fields = (fields,) 14 self.fields = [] 15 for field in fields: 16 if isinstance(field, basestring): 17 field = model._meta.get_field(field) 18 self.fields.append(field) 19 20 # Add a convenience accessor for serve, download_url, 21 # and get_storage_backend which only act on a single FileField 22 def _get_field(self): 23 if len(self.fields) != 1: 24 raise ValueError('Expected only a single FileField, got %d' % len(self.fields)) 25 return self.fields[0] 26 field = property(_get_field) 27 28 def prepare_upload(self, request, url): 29 return None 30 31 def serve(self, request, file, save_as=False, content_type=None): 32 return None 33 34 def download_url(self, file): 35 return None 36 37 def get_storage_backend(self): 38 return None 39 40 # Public API 41 def prepare_upload(model, fields, request, url=None): 42 if not url: 43 url = request.get_full_path() 44 for backend in _get_backends(model, fields): 45 result = backend.prepare_upload(request, url) 46 if result is not None: 47 return result 48 49 # By default we simply return the URL unmodified 50 return url, {} 51 52 def serve_file(model, fields, request, file, save_as=False, content_type=None): 53 filename = file.name.rsplit('/')[-1] 54 if save_as is True: 55 save_as = filename 56 if not content_type: 57 content_type = mimetypes.guess_type(filename)[0] 58 for backend in _get_backends(model, fields): 59 result = backend.serve(request, file, save_as=save_as, content_type=content_type) 60 if result is not None: 61 return result 62 63 return _default_serve_file(file, save_as, content_type) 64 65 def download_url(model, fields, file): 66 for backend in _get_backends(model, fields): 67 result = backend.download_url(file) 68 if result is not None: 69 return result 70 71 # By default we use MEDIA_URL 72 return settings.MEDIA_URL + file.name 73 74 def get_storage_backend(model, fields): 75 for backend in _get_backends(model, fields): 76 result = backend.get_storage_backend() 77 if result is not None: 78 return result 79 80 # By default there is no public download URL 81 return default_storage 82 83 # Internal utilities 84 class ChunkedFile(object): 85 def __init__(self, file): 86 self.file = file 87 88 def __iter__(self): 89 return self.file.chunks() 90 91 def _default_serve_file(file, save_as, content_type): 92 """ 93 Serves the file in chunks for efficiency reasons. 94 95 The transfer still goes through Django itself, so it's much worse than 96 using the web server, but at least it works with all configurations. 97 """ 98 response = HttpResponse(ChunkedFile(file), content_type=content_type) 99 if save_as: 100 response['Content-Disposition'] = smart_str(u'attachment; filename=%s' % save_as) 101 if file.size is not None: 102 response['Content-Length'] = file.size 103 return response 104 105 def _get_backends(model, fields): 106 return (_load_backend(name, model, fields) 107 for name in settings.FILE_BACKENDS) 108 109 def _load_backend(path, model, fields): 110 module_name, attr_name = path.rsplit('.', 1) 111 try: 112 mod = import_module(module_name) 113 except ImportError, e: 114 raise ImproperlyConfigured('Error importing file backend module %s: "%s"' % (module_name, e)) 115 except ValueError, e: 116 raise ImproperlyConfigured('Error importing file backend module. Is FILE_BACKENDS a correctly defined list or tuple?') 117 try: 118 backend = getattr(mod, attr_name) 119 except AttributeError: 120 raise ImproperlyConfigured('Module "%s" does not define a "%s" file backend' % (module_name, attr_name)) 121 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 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 … … 224 232 if arg in kwargs: 225 233 raise TypeError("'%s' is not a valid argument for %s." % (arg, self.__class__)) 226 234 227 self.storage = storage or default_storage235 self.storage = storage 228 236 self.upload_to = upload_to 229 237 if callable(upload_to): 230 238 self.generate_filename = upload_to … … 257 265 258 266 def contribute_to_class(self, cls, name): 259 267 super(FileField, self).contribute_to_class(cls, name) 268 if self.storage is None: 269 self.storage = get_storage_backend(self.model, self) 260 270 setattr(cls, self.name, self.descriptor_class(self)) 261 271 signals.post_delete.connect(self.delete_file, sender=cls) 262 272 -
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 -
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 is models.FileModel: 9 return '/redirect' + url, {'x-extra': 'abcd', 'x-data': 'efg'} 10 11 def serve(self, request, file, *args, **kwargs): 12 if self.model is models.FileModel and not file.name.endswith('pass'): 13 return HttpResponseRedirect('/download/') 14 15 def download_url(self, file): 16 if self.model is models.FileModel 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__ and \ 21 self.model.__name__ == 'FileModel': 22 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)