Django

Code

Ticket #2070: 5313_updated_file_progress.diff

File 5313_updated_file_progress.diff, 36.7 kB (added by Michael Axiak <axiak@mit.edu>, 1 year ago)

Removed some unneeded things. No file progress tracking by default.

  • django/http/file_descriptor.py

    old new  
     1""" 
     2This file contains a fallback FileProgressDescriptor 
     3for file upload progress. 
     4""" 
     5 
     6class NullFileProgressDescriptor(object): 
     7    """ 
     8    This descriptor doesn't do anything, but 
     9    is here to be overridden. 
     10    """ 
     11 
     12    def __init__(self, exception): 
     13        pass 
     14 
     15    def __get__(self, request, HttpRequest): 
     16        return {} 
     17 
     18    def __set__(self, request, new_progress): 
     19        return None 
     20 
     21    def __delete__(self, request): 
     22        return None 
  • django/http/__init__.py

    old new  
    1 import os 
     1import os, pickle 
    22from Cookie import SimpleCookie 
    33from pprint import pformat 
    44from urllib import urlencode, quote 
    55from django.utils.datastructures import MultiValueDict 
     6from django.http.file_descriptor import NullFileProgressDescriptor 
     7import re 
    68 
     9try: 
     10    from cStringIO import StringIO 
     11except ImportError: 
     12    from StringIO import StringIO 
     13 
    714RESERVED_CHARS="!*'();:@&=+$,/?%#[]" 
    815 
    916try: 
     
    1724 
    1825class HttpRequest(object): 
    1926    "A basic HTTP request" 
     27    upload_id_re = re.compile(r'^[a-fA-F0-9]{32}$') 
     28 
    2029    def __init__(self): 
    2130        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} 
    2231        self.path = '' 
     
    4251    def is_secure(self): 
    4352        return os.environ.get("HTTPS") == "on" 
    4453 
    45 def parse_file_upload(header_dict, post_data): 
    46     "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)" 
    47     import email, email.Message 
    48     from cgi import parse_header 
    49     raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()]) 
    50     raw_message += '\r\n\r\n' + post_data 
    51     msg = email.message_from_string(raw_message) 
    52     POST = MultiValueDict() 
    53     FILES = MultiValueDict() 
    54     for submessage in msg.get_payload(): 
    55         if submessage and isinstance(submessage, email.Message.Message): 
    56             name_dict = parse_header(submessage['Content-Disposition'])[1] 
    57             # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads 
    58             # or {'name': 'blah'} for POST fields 
    59             # We assume all uploaded files have a 'filename' set. 
    60             if 'filename' in name_dict: 
    61                 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported" 
    62                 if not name_dict['filename'].strip(): 
    63                     continue 
    64                 # IE submits the full path, so trim everything but the basename. 
    65                 # (We can't use os.path.basename because it expects Linux paths.) 
    66                 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:] 
    67                 FILES.appendlist(name_dict['name'], { 
    68                     'filename': filename, 
    69                     'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None, 
    70                     'content': submessage.get_payload(), 
    71                 }) 
    72             else: 
    73                 POST.appendlist(name_dict['name'], submessage.get_payload()) 
    74     return POST, FILES 
     54    def _get_file_progress_from_args(self, headers, get, querystring): 
     55        if 'X-Upload-ID' in headers: 
     56            progress_id = headers['X-Upload-ID'] 
     57        elif 'X-Progress-ID' in headers: 
     58            progress_id = headers['X-Progress-ID'] 
     59        elif 'HTTP_X_UPLOAD_ID' in headers: 
     60            progress_id = headers['HTTP_X_UPLOAD_ID'] 
     61        elif 'HTTP_X_PROGRESS_ID' in headers: 
     62            progress_id = headers['HTTP_X_PROGRESS_ID'] 
     63        elif 'upload_id' in get: 
     64            progress_id = get['upload_id'] 
     65        elif 'progress_id' in get: 
     66            progress_id = get['progress_id'] 
     67        elif querystring and len(querystring.strip()) == 32: 
     68            progress_id = querystring 
     69        else: 
     70            return None 
    7571 
     72        if not self.upload_id_re.match(progress_id): 
     73            return None 
     74 
     75        return progress_id 
     76 
     77def parse_file_upload(headers, input, request): 
     78    from django.conf import settings 
     79 
     80    # Only stream files to disk if FILE_STREAMING_DIR is set 
     81    file_upload_dir = settings.FILE_UPLOAD_DIR 
     82    streaming_min_post_size = settings.STREAMING_MIN_POST_SIZE 
     83 
     84    try: 
     85        parser = MultiPartParser(headers, input, request, file_upload_dir, streaming_min_post_size) 
     86        return parser.parse() 
     87    except MultiPartParserError, e: 
     88        return MultiValueDict({ '_file_upload_error': [e.message] }), {} 
     89 
     90class MultiPartParserError(Exception): 
     91    def __init__(self, message): 
     92        self.message = message 
     93    def __str__(self): 
     94        return repr(self.message) 
     95 
     96class MultiPartParser(object): 
     97    """ 
     98    A rfc2388 multipart/form-data parser. 
     99     
     100    parse() reads the input stream in chunk_size chunks and returns a 
     101    tuple of (POST MultiValueDict, FILES MultiValueDict). If 
     102    file_upload_dir is defined files will be streamed to temporary 
     103    files in the specified directory. 
     104 
     105    The FILES dictionary will have 'filename', 'content-type', 
     106    'content' and 'content-length' entries. For streamed files it will 
     107    also have 'tmpfilename' and 'tmpfile'. The 'content' entry will 
     108    only be read from disk when referenced for streamed files. 
     109 
     110    If the X-Progress-ID is sent (in one of many formats), then 
     111    object.file_progress will be given a dictionary of the progress. 
     112    """ 
     113    def __init__(self, headers, input, request, file_upload_dir=None, streaming_min_post_size=None, chunk_size=1024*64): 
     114        try: 
     115            content_length = int(headers['Content-Length']) 
     116        except: 
     117            raise MultiPartParserError('Invalid Content-Length: %s' % headers.get('Content-Length')) 
     118 
     119        content_type = headers.get('Content-Type') 
     120 
     121        if not content_type or not content_type.startswith('multipart/'): 
     122            raise MultiPartParserError('Invalid Content-Type: %s' % content_type) 
     123             
     124        ctype, opts = self.parse_header(content_type) 
     125        boundary = opts.get('boundary') 
     126        from cgi import valid_boundary 
     127        if not boundary or not valid_boundary(boundary): 
     128            raise MultiPartParserError('Invalid boundary in multipart form: %s' % boundary) 
     129 
     130        progress_id = request.META['UPLOAD_PROGRESS_ID'] 
     131 
     132        if file_upload_dir and progress_id: 
     133            self._progress_filename = os.path.join(file_upload_dir, progress_id) 
     134        else: 
     135            self._progress_filename = None 
     136        self._boundary = '--' + boundary 
     137        self._input = input 
     138        self._size = content_length 
     139        self._received = 0 
     140        self._file_upload_dir = file_upload_dir 
     141        self._chunk_size = chunk_size 
     142        self._state = 'PREAMBLE' 
     143        self._partial = '' 
     144        self._post = MultiValueDict() 
     145        self._files = MultiValueDict() 
     146        self._request = request 
     147 
     148        if streaming_min_post_size is not None and content_length < streaming_min_post_size: 
     149            self._file_upload_dir = None # disable file streaming for small request 
     150        elif self._progress_filename: 
     151            request.file_progress = {'state': 'starting'} 
     152 
     153        try: 
     154            # Use mx fast string search if available. 
     155            from mx.TextTools import FS 
     156            self._fs = FS(self._boundary) 
     157        except ImportError: 
     158            self._fs = None 
     159 
     160    def parse(self): 
     161        try: 
     162            self._parse() 
     163        finally: 
     164            if self._progress_filename: 
     165                self._request.file_progress = {'state': 'done'} 
     166        return self._post, self._files 
     167 
     168    def _parse(self): 
     169        size = self._size 
     170 
     171        try: 
     172            while size > 0: 
     173                n = self._read(self._input, min(self._chunk_size, size)) 
     174                if not n: 
     175                    break 
     176                size -= n 
     177        except: 
     178            # consume any remaining data so we dont generate a "Connection Reset" error 
     179            size = self._size - self._received 
     180            while size > 0: 
     181                data = self._input.read(min(self._chunk_size, size)) 
     182                size -= len(data) 
     183            raise 
     184 
     185    def _find_boundary(self, data, start, stop): 
     186        """ 
     187        Find the next boundary and return the end of current part 
     188        and start of next part. 
     189        """ 
     190        if self._fs: 
     191            boundary = self._fs.find(data, start, stop) 
     192        else: 
     193            boundary = data.find(self._boundary, start, stop) 
     194        if boundary >= 0: 
     195            end = boundary 
     196            next = boundary + len(self._boundary) 
     197 
     198            # backup over CRLF 
     199            if end > 0 and data[end-1] == '\n': end -= 1 
     200            if end > 0 and data[end-1] == '\r': end -= 1 
     201            # skip over --CRLF 
     202            if next < stop and data[next] == '-': next += 1 
     203            if next < stop and data[next] == '-': next += 1 
     204            if next < stop and data[next] == '\r': next += 1 
     205            if next < stop and data[next] == '\n': next += 1 
     206 
     207            return True, end, next 
     208        else: 
     209            return False, stop, stop 
     210 
     211    class TemporaryFile(object): 
     212        "A temporary file that tries to delete itself when garbage collected." 
     213        def __init__(self, dir): 
     214            import tempfile 
     215            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir) 
     216            self.file = os.fdopen(fd, 'w+b') 
     217            self.name = name 
     218 
     219        def __getattr__(self, name): 
     220            a = getattr(self.__dict__['file'], name) 
     221            if type(a) != type(0): 
     222                setattr(self, name, a) 
     223            return a 
     224 
     225        def __del__(self): 
     226            try: 
     227                os.unlink(self.name) 
     228            except OSError: 
     229                pass 
     230 
     231    class LazyContent(dict): 
     232        """ 
     233        A lazy FILES dictionary entry that reads the contents from 
     234        tmpfile only when referenced. 
     235        """ 
     236        def __init__(self, data): 
     237            dict.__init__(self, data) 
     238 
     239        def __getitem__(self, key): 
     240            if key == 'content' and not self.has_key(key): 
     241                self['tmpfile'].seek(0) 
     242                self['content'] = self['tmpfile'].read() 
     243            return dict.__getitem__(self, key) 
     244 
     245    def _read(self, input, size): 
     246        data = input.read(size) 
     247 
     248        if not data: 
     249            return 0 
     250 
     251        read_size = len(data) 
     252        self._received += read_size 
     253 
     254        if self._partial: 
     255            data = self._partial + data 
     256 
     257        start = 0 
     258        stop = len(data) 
     259 
     260        while start < stop: 
     261            boundary, end, next = self._find_boundary(data, start, stop) 
     262 
     263            if not boundary and read_size: 
     264                # make sure we dont treat a partial boundary (and its separators) as data 
     265                stop -= len(self._boundary) + 16 
     266                end = next = stop 
     267                if end <= start: 
     268                    break # need more data 
     269 
     270            if self._state == 'PREAMBLE': 
     271                # Preamble, just ignore it 
     272                self._state = 'HEADER' 
     273 
     274            elif self._state == 'HEADER': 
     275                # Beginning of header, look for end of header and parse it if found. 
     276 
     277                header_end = data.find('\r\n\r\n', start, stop) 
     278                if header_end == -1: 
     279                    break # need more data 
     280 
     281                header = data[start:header_end] 
     282 
     283                self._fieldname = None 
     284                self._filename = None 
     285                self._content_type = None 
     286 
     287                for line in header.split('\r\n'): 
     288                    ctype, opts = self.parse_header(line) 
     289                    if ctype == 'content-disposition: form-data': 
     290                        self._fieldname = opts.get('name') 
     291                        self._filename = opts.get('filename') 
     292                    elif ctype.startswith('content-type: '): 
     293                        self._content_type = ctype[14:] 
     294 
     295                if self._filename is not None: 
     296                    # cleanup filename from IE full paths: 
     297                    self._filename = self._filename[self._filename.rfind("\\")+1:].strip() 
     298 
     299                    if self._filename: # ignore files without filenames 
     300                        if self._file_upload_dir: 
     301                            try: 
     302                                self._file = self.TemporaryFile(dir=self._file_upload_dir) 
     303                            except: 
     304                                raise MultiPartParserError("Failed to create temporary file.") 
     305                        else: 
     306                            self._file = StringIO() 
     307                    else: 
     308                        self._file = None 
     309                    self._filesize = 0 
     310                    self._state = 'FILE' 
     311                else: 
     312                    self._field = StringIO() 
     313                    self._state = 'FIELD' 
     314                next = header_end + 4 
     315 
     316            elif self._state == 'FIELD': 
     317                # In a field, collect data until a boundary is found. 
     318 
     319                self._field.write(data[start:end]) 
     320                if boundary: 
     321                    if self._fieldname: 
     322                        self._post.appendlist(self._fieldname, self._field.getvalue()) 
     323                    self._field.close() 
     324                    self._state = 'HEADER' 
     325 
     326            elif self._state == 'FILE': 
     327                # In a file, collect data until a boundary is found. 
     328 
     329                if self._file: 
     330                    try: 
     331                        self._file.write(data[start:end]) 
     332                    except IOError, e: 
     333                        raise MultiPartParserError("Failed to write to temporary file.") 
     334                    self._filesize += end-start 
     335 
     336                    if self._progress_filename: 
     337                        self._request.file_progress = {'received': self._received, 
     338                                                       'size':     self._size, 
     339                                                       'state':    'uploading'} 
     340 
     341                if boundary: 
     342                    if self._file: 
     343                        if self._file_upload_dir: 
     344                            self._file.seek(0) 
     345                            file = self.LazyContent({ 
     346                                'filename': self._filename, 
     347                                'content-type':  self._content_type, 
     348                                # 'content': is read on demand 
     349                                'content-length': self._filesize, 
     350                                'tmpfilename': self._file.name, 
     351                                'tmpfile': self._file 
     352                            }) 
     353                        else: 
     354                            file = { 
     355                                'filename': self._filename, 
     356                                'content-type':  self._content_type, 
     357                                'content': self._file.getvalue(), 
     358                                'content-length': self._filesize 
     359                            } 
     360                            self._file.close() 
     361 
     362                        self._files.appendlist(self._fieldname, file) 
     363 
     364                    self._state = 'HEADER' 
     365 
     366            start = next 
     367 
     368        self._partial = data[start:] 
     369 
     370        return read_size 
     371 
     372    def parse_header(self, line): 
     373        from cgi import parse_header 
     374        return parse_header(line) 
     375 
    76376class QueryDict(MultiValueDict): 
    77377    """A specialized MultiValueDict that takes a query string when initialized. 
    78378    This is immutable unless you create a copy of it.""" 
  • django/oldforms/__init__.py

    old new  
    666666        self.validator_list = [self.isNonEmptyFile] + validator_list 
    667667 
    668668    def isNonEmptyFile(self, field_data, all_data): 
    669         try
    670             content = field_data['content'] 
    671         except TypeError
     669        if field_data.has_key('_file_upload_error')
     670            raise validators.CriticalValidationError, field_data['_file_upload_error'] 
     671        if not field_data.has_key('filename')
    672672            raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.") 
    673         if not content
     673        if not field_data['content-length']
    674674            raise validators.CriticalValidationError, gettext("The submitted file is empty.") 
    675675 
    676676    def render(self, data): 
    677677        return '<input type="file" id="%s" class="v%s" name="%s" />' % \ 
    678678            (self.get_id(), self.__class__.__name__, self.field_name) 
    679679 
     680    def prepare(self, new_data): 
     681        if new_data.has_key('_file_upload_error'): 
     682            # pretend we got something in the field to raise a validation error later 
     683            new_data[self.field_name] = { '_file_upload_error': new_data['_file_upload_error'] } 
     684 
    680685    def html2python(data): 
    681686        if data is None: 
    682687            raise EmptyValue 
  • django/db/models/base.py

    old new  
    1212from django.dispatch import dispatcher 
    1313from django.utils.datastructures import SortedDict 
    1414from django.utils.functional import curry 
     15from django.utils.file import file_move_safe 
    1516from django.conf import settings 
    1617from itertools import izip 
    1718import types 
    1819import sys 
    1920import os 
    2021 
     22                 
    2123class ModelBase(type): 
    2224    "Metaclass for all models" 
    2325    def __new__(cls, name, bases, attrs): 
     
    361363    def _get_FIELD_size(self, field): 
    362364        return os.path.getsize(self._get_FIELD_filename(field)) 
    363365 
    364     def _save_FIELD_file(self, field, filename, raw_contents, save=True): 
     366    def _save_FIELD_file(self, field, filename, raw_field, save=True): 
    365367        directory = field.get_directory_name() 
    366368        try: # Create the date-based directory if it doesn't exist. 
    367369            os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 
     
    383385        setattr(self, field.attname, filename) 
    384386 
    385387        full_filename = self._get_FIELD_filename(field) 
    386         fp = open(full_filename, 'wb') 
    387         fp.write(raw_contents) 
    388         fp.close() 
     388        if raw_field.has_key('tmpfilename'): 
     389            raw_field['tmpfile'].close() 
     390            file_move_safe(raw_field['tmpfilename'], full_filename) 
     391        else: 
     392            fp = open(full_filename, 'wb') 
     393            fp.write(raw_field['content']) 
     394            fp.close() 
    389395 
    390396        # Save the width and/or height, if applicable. 
    391397        if isinstance(field, ImageField) and (field.width_field or field.height_field): 
  • django/db/models/fields/__init__.py

    old new  
    701701        setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) 
    702702        setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) 
    703703        setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) 
    704         setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) 
     704        setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save)) 
    705705        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) 
    706706 
    707707    def delete_file(self, instance): 
     
    724724        if new_data.get(upload_field_name, False): 
    725725            func = getattr(new_object, 'save_%s_file' % self.name) 
    726726            if rel: 
    727                 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save) 
     727                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0], save) 
    728728            else: 
    729                 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) 
     729                func(new_data[upload_field_name]["filename"], new_data[upload_field_name], save) 
    730730 
    731731    def get_directory_name(self): 
    732732        return os.path.normpath(datetime.datetime.now().strftime(self.upload_to)) 
  • django/conf/global_settings.py

    old new  
    242242# isExistingURL validator. 
    243243URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" 
    244244 
     245# The directory to place streamed file uploads. The web server needs write 
     246# permissions on this directory. 
     247# If this is None, streaming uploads are disabled. 
     248FILE_UPLOAD_DIR = None 
     249 
     250# The minimum size of a POST before file uploads are streamed to disk. 
     251# Any less than this number, and the file is uploaded to memory. 
     252# Size is in bytes. 
     253STREAMING_MIN_POST_SIZE = 512 * (2**10) 
     254 
    245255############## 
    246256# MIDDLEWARE # 
    247257############## 
  • django/core/handlers/wsgi.py

    old new  
    7575        self.environ = environ 
    7676        self.path = environ['PATH_INFO'] 
    7777        self.META = environ 
     78        self.META['UPLOAD_PROGRESS_ID'] = self._get_file_progress_id() 
    7879        self.method = environ['REQUEST_METHOD'].upper() 
    7980 
    8081    def __repr__(self): 
     
    111112            if self.environ.get('CONTENT_TYPE', '').startswith('multipart'): 
    112113                header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')]) 
    113114                header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '') 
    114                 self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data) 
     115                header_dict['Content-Length'] = self.environ.get('CONTENT_LENGTH', '') 
     116                header_dict['X-Progress-ID'] = self.environ.get('HTTP_X_PROGRESS_ID', '') 
     117                try: 
     118                    self._post, self._files = http.parse_file_upload(header_dict, self.environ['wsgi.input'], self) 
     119                except: 
     120                    self._post, self._files = {}, {} # make sure we dont read the input stream again 
     121                    raise 
     122                self._raw_post_data = None # raw data is not available for streamed multipart messages 
    115123            else: 
    116124                self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict() 
    117125        else: 
     
    167175            buf.close() 
    168176            return self._raw_post_data 
    169177 
     178    def _get_file_progress_id(self): 
     179        """ 
     180        Returns the Progress ID of the request, 
     181        usually provided if there is a file upload 
     182        going on. 
     183        Returns ``None`` if no progress ID is specified. 
     184        """ 
     185        return self._get_file_progress_from_args(self.environ, 
     186                                                 self.GET, 
     187                                                 self.environ.get('QUERY_STRING', '')) 
     188 
    170189    GET = property(_get_get, _set_get) 
    171190    POST = property(_get_post, _set_post) 
    172191    COOKIES = property(_get_cookies, _set_cookies) 
  • django/core/handlers/base.py

    old new  
    55 
    66class BaseHandler(object): 
    77    def __init__(self): 
    8         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None 
     8        self._upload_middleware = self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None 
    99 
    1010    def load_middleware(self): 
    1111        """ 
     
    1919        self._view_middleware = [] 
    2020        self._response_middleware = [] 
    2121        self._exception_middleware = [] 
     22        self._upload_middleware = [] 
    2223        for middleware_path in settings.MIDDLEWARE_CLASSES: 
    2324            try: 
    2425                dot = middleware_path.rindex('.') 
     
    4748                self._response_middleware.insert(0, mw_instance.process_response) 
    4849            if hasattr(mw_instance, 'process_exception'): 
    4950                self._exception_middleware.insert(0, mw_instance.process_exception) 
     51            if hasattr(mw_instance, 'process_upload'): 
     52                self._upload_middleware.append(mw_instance.process_upload) 
    5053 
     54    def file_progress_descriptor(self, request): 
     55        """ 
     56        Returns a descriptor that manages the file_progress 
     57        """ 
     58        for mw_call in self._upload_middleware: 
     59            result = mw_call(http.MultiPartParserError) 
     60            if result != None: 
     61                return result 
     62        return http.NullFileProgressDescriptor(http.MultiPartParserError) 
     63 
    5164    def get_response(self, request): 
    5265        "Returns an HttpResponse object for the given HttpRequest" 
    5366        from django.core import exceptions, urlresolvers 
    5467        from django.core.mail import mail_admins 
    5568        from django.conf import settings 
    5669 
     70        # Add file_progress descriptor 
     71        request.__class__.file_progress = self.file_progress_descriptor(request) 
     72 
    5773        # Apply request middleware 
    5874        for middleware_method in self._request_middleware: 
    5975            response = middleware_method(request) 
  • django/core/handlers/modpython.py

    old new  
    4747    def _load_post_and_files(self): 
    4848        "Populates self._post and self._files" 
    4949        if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'): 
    50             self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data) 
     50            self._raw_post_data = None # raw data is not available for streamed multipart messages 
     51            try: 
     52                self._post, self._files = http.parse_file_upload(self._req.headers_in, self._req, self) 
     53            except: 
     54                self._post, self._files = {}, {} # make sure we dont read the input stream again 
     55                raise 
    5156        else: 
    5257            self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict() 
    5358 
     
    9297                'AUTH_TYPE':         self._req.ap_auth_type, 
    9398                'CONTENT_LENGTH':    self._req.clength, # This may be wrong 
    9499                'CONTENT_TYPE':      self._req.content_type, # This may be wrong 
    95                 'GATEWAY_INTERFACE': 'CGI/1.1', 
    96                 'PATH_INFO':         self._req.path_info, 
    97                 'PATH_TRANSLATED':   None, # Not supported 
    98                 'QUERY_STRING':      self._req.args, 
    99                 'REMOTE_ADDR':       self._req.connection.remote_ip, 
    100                 'REMOTE_HOST':       None, # DNS lookups not supported 
    101                 'REMOTE_IDENT':      self._req.connection.remote_logname, 
    102                 'REMOTE_USER':       self._req.user, 
    103                 'REQUEST_METHOD':    self._req.method, 
    104                 'SCRIPT_NAME':       None, # Not supported 
    105                 'SERVER_NAME':       self._req.server.server_hostname, 
    106                 'SERVER_PORT':       self._req.server.port, 
    107                 'SERVER_PROTOCOL':   self._req.protocol, 
    108                 'SERVER_SOFTWARE':   'mod_python' 
     100                'GATEWAY_INTERFACE':  'CGI/1.1', 
     101                'PATH_INFO':          self._req.path_info, 
     102                'PATH_TRANSLATED':    None, # Not supported 
     103                'QUERY_STRING':       self._req.args, 
     104                'REMOTE_ADDR':        self._req.connection.remote_ip, 
     105                'REMOTE_HOST':        None, # DNS lookups not supported 
     106                'REMOTE_IDENT':       self._req.connection.remote_logname, 
     107                'REMOTE_USER':        self._req.user, 
     108                'REQUEST_METHOD':     self._req.method, 
     109                'SCRIPT_NAME':        None, # Not supported 
     110                'SERVER_NAME':        self._req.server.server_hostname, 
     111                'SERVER_PORT':        self._req.server.port, 
     112                'SERVER_PROTOCOL':    self._req.protocol, 
     113                'UPLOAD_PROGRESS_ID': self._get_file_progress_id(), 
     114                'SERVER_SOFTWARE':    'mod_python' 
    109115            } 
    110116            for key, value in self._req.headers_in.items(): 
    111117                key = 'HTTP_' + key.upper().replace('-', '_') 
     
    122128    def _get_method(self): 
    123129        return self.META['REQUEST_METHOD'].upper() 
    124130 
     131    def _get_file_progress_id(self): 
     132        """ 
     133        Returns the Progress ID of the request, 
     134        usually provided if there is a file upload 
     135        going on. 
     136        Returns ``None`` if no progress ID is specified. 
     137        """ 
     138        return self._get_file_progress_from_args(self._req.headers_in, 
     139                                                 self.GET, 
     140                                                 self._req.args) 
     141 
    125142    GET = property(_get_get, _set_get) 
    126143    POST = property(_get_post, _set_post) 
    127144    COOKIES = property(_get_cookies, _set_cookies) 
  • django/utils/file.py

    old new  
     1import os 
     2 
     3try: 
     4    import shutils 
     5    file_move = shutils.move 
     6except: 
     7    file_move = os.rename 
     8 
     9def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64): 
     10    """ 
     11    Moves a file from one location to another in the safest way possible. 
     12     
     13    First, it tries using shutils.move, which is OS-dependent but doesn't 
     14    break with change of filesystems. Then it tries os.rename, which will 
     15    break if it encounters a change in filesystems. Lastly, it streams 
     16    it manually from one file to another in python. 
     17    """ 
     18 
     19    if old_file_name == new_file_name: 
     20        # No file moving takes place. 
     21        return 
     22     
     23    try: 
     24        file_move(old_file_name, new_file_name) 
     25        return 
     26    except: 
     27        pass 
     28     
     29    new_file = open(new_file_name, 'wb') 
     30    old_file = open(old_file_name, 'rb') 
     31    current_chunk = None 
     32     
     33    while current_chunk != '': 
     34        current_chunk = old_file.read(chunk_size) 
     35        new_file.write(current_chunk) 
     36         
     37    new_file.close() 
     38    old_file.close() 
     39 
     40    os.remove(old_file_name) 
  • tests/modeltests/test_client/views.py

    old new  
    4646 
    4747    return HttpResponse(t.render(c)) 
    4848 
     49def post_file_view(request): 
     50    "A view that expects a multipart post and returns a file in the context" 
     51    t = Template('File {{ file.filename }} received', name='POST Template') 
     52    c = Context({'file': request.FILES['file_file']}) 
     53    return HttpResponse(t.render(c)) 
     54 
    4955def redirect_view(request): 
    5056    "A view that redirects all requests to the GET view" 
    5157    return HttpResponseRedirect('/test_client/get_view/') 
  • tests/modeltests/test_client/models.py

    old new  
    33 
    44The test client is a class that can act like a simple 
    55browser for testing purposes. 
    6    
     6 
    77It allows the user to compose GET and POST requests, and 
    88obtain the response that the server gave to those requests. 
    99The server Response objects are annotated with the details 
     
    7676        self.assertEqual(response.template.name, "Book template") 
    7777        self.assertEqual(response.content, "Blink - Malcolm Gladwell") 
    7878 
     79    def test_post_file_view(self): 
     80        "POST this python file to a view" 
     81        import os, tempfile 
     82        from django.conf import settings 
     83        file = __file__.replace('.pyc', '.py') 
     84        for upload_dir in [None, tempfile.gettempdir()]: 
     85            settings.FILE_UPLOAD_DIR = upload_dir 
     86            post_data = { 'name': file, 'file': open(file) } 
     87            response = self.client.post('/test_client/post_file_view/', post_data) 
     88            self.failUnless('models.py' in response.context['file']['filename']) 
     89            self.failUnless(len(response.context['file']['content']) == os.path.getsize(file)) 
     90            if upload_dir: 
     91                self.failUnless(response.context['file']['tmpfilename']) 
     92 
    7993    def test_redirect(self): 
    8094        "GET a URL that redirects elsewhere" 
    8195        response = self.client.get('/test_client/redirect_view/') 
  • tests/modeltests/test_client/urls.py

    old new  
    55urlpatterns = patterns('', 
    66    (r'^get_view/$', views.get_view), 
    77    (r'^post_view/$', views.post_view), 
     8    (r'^post_file_view/$', views.post_file_view), 
    89    (r'^raw_post_view/$', views.raw_post_view), 
    910    (r'^redirect_view/$', views.redirect_view), 
    1011    (r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_view/' }), 
  • docs/request_response.txt

    old new  
    7272``FILES`` 
    7373    A dictionary-like object containing all uploaded files. Each key in 
    7474    ``FILES`` is the ``name`` from the ``<input type="file" name="" />``. Each 
    75     value in ``FILES`` is a standard Python dictionary with the following three 
     75    value in ``FILES`` is a standard Python dictionary with the following four 
    7676    keys: 
    7777 
    7878        * ``filename`` -- The name of the uploaded file, as a Python string. 
    7979        * ``content-type`` -- The content type of the uploaded file. 
    8080        * ``content`` -- The raw content of the uploaded file. 
     81        * ``content-length`` -- The length of the content in bytes. 
    8182 
     83    If streaming file uploads are enabled two additional keys 
     84    describing the uploaded file will be present: 
     85 
     86        * ``tmpfilename`` -- The filename for the temporary file. 
     87        * ``tmpfile`` -- An open file object for the temporary file. 
     88 
     89    The temporary file will be removed when the request finishes. 
     90 
     91    Note that accessing ``content`` when streaming uploads are enabled 
     92    will read the whole file into memory which may not be what you want. 
     93 
    8294    Note that ``FILES`` will only contain data if the request method was POST 
    8395    and the ``<form>`` that posted to the request had 
    8496    ``enctype="multipart/form-data"``. Otherwise, ``FILES`` will be a blank 
  • docs/settings.txt

    old new  
    448448 
    449449.. _Testing Django Applications: ../testing/ 
    450450 
     451FILE_UPLOAD_DIR 
     452--------------- 
     453 
     454Default: ``None`` 
     455 
     456Path to a directory where temporary files should be written during 
     457file uploads. Leaving this as ``None`` will disable streaming file uploads, 
     458and cause all uploaded files to be stored (temporarily) in memory. 
     459 
    451460IGNORABLE_404_ENDS 
    452461------------------ 
    453462 
     
    764773 
    765774.. _site framework docs: ../sites/ 
    766775 
     776STREAMING_MIN_POST_SIZE 
     777----------------------- 
     778 
     779Default: 524288 (``512*1024``) 
     780 
     781An integer specifying the minimum number of bytes that has to be 
     782received (in a POST) for file upload streaming to take place. Any 
     783request smaller than this will be handled in memory.  
     784Note: ``FILE_UPLOAD_DIR`` has to be defined to enable streaming. 
     785 
    767786TEMPLATE_CONTEXT_PROCESSORS 
    768787--------------------------- 
    769788 
  • docs/forms.txt

    old new  
    475475   new_data = request.POST.copy() 
    476476   new_data.update(request.FILES) 
    477477 
     478Streaming file uploads. 
     479----------------------- 
     480 
     481File uploads will be read into memory by default. This works fine for 
     482small to medium sized uploads (from 1MB to 100MB depending on your 
     483setup and usage). If you want to support larger uploads you can enable 
     484upload streaming where only a small part of the file will be in memory 
     485at any time. To do this you need to specify the ``FILE_UPLOAD_DIR`` 
     486setting (see the settings_ document for more details). 
     487 
     488See `request object`_ for more details about ``request.FILES`` objects 
     489with streaming file uploads enabled. 
     490 
    478491Validators 
    479492========== 
    480493 
     
    698711.. _`generic views`: ../generic_views/ 
    699712.. _`models API`: ../model-api/ 
    700713.. _settings: ../settings/ 
     714.. _request object: ../request_response/#httprequest-objects