Code

Ticket #7614: 7814_cleanups.diff

File 7814_cleanups.diff, 25.5 KB (added by axiak, 6 years ago)

Update to the patch...thanks vomjom!

Line 
1Index: django/db/models/base.py
2===================================================================
3--- django/db/models/base.py    (revision 7857)
4+++ django/db/models/base.py    (working copy)
5@@ -536,7 +536,7 @@
6             # This is a normal uploadedfile that we can stream.
7             fp = open(full_filename, 'wb')
8             locks.lock(fp, locks.LOCK_EX)
9-            for chunk in raw_field.chunk():
10+            for chunk in raw_field.chunks():
11                 fp.write(chunk)
12             locks.unlock(fp)
13             fp.close()
14Index: django/db/models/fields/__init__.py
15===================================================================
16--- django/db/models/fields/__init__.py (revision 7857)
17+++ django/db/models/fields/__init__.py (working copy)
18@@ -766,9 +766,12 @@
19     def get_db_prep_save(self, value):
20         "Returns field's value prepared for saving into a database."
21         # Need to convert UploadedFile objects provided via a form to unicode for database insertion
22-        if value is None:
23+        if hasattr(value, 'name'):
24+            return value.name
25+        elif value is None:
26             return None
27-        return unicode(value)
28+        else:
29+            return unicode(value)
30 
31     def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
32         field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
33@@ -842,7 +845,7 @@
34             # We don't need to raise a warning because Model._save_FIELD_file will
35             # do so for us.
36             try:
37-                file_name = file.file_name
38+                file_name = file.name
39             except AttributeError:
40                 file_name = file['filename']
41 
42@@ -857,9 +860,9 @@
43         return os.path.normpath(f)
44 
45     def save_form_data(self, instance, data):
46-        from django.newforms.fields import UploadedFile
47+        from django.core.files.uploadedfile import UploadedFile
48         if data and isinstance(data, UploadedFile):
49-            getattr(instance, "save_%s_file" % self.name)(data.filename, data.data, save=False)
50+            getattr(instance, "save_%s_file" % self.name)(data.name, data, save=False)
51 
52     def formfield(self, **kwargs):
53         defaults = {'form_class': forms.FileField}
54Index: django/oldforms/__init__.py
55===================================================================
56--- django/oldforms/__init__.py (revision 7857)
57+++ django/oldforms/__init__.py (working copy)
58@@ -686,7 +686,7 @@
59             if upload_errors:
60                 raise validators.CriticalValidationError, upload_errors
61         try:
62-            file_size = new_data.file_size
63+            file_size = new_data.size
64         except AttributeError:
65             file_size = len(new_data['content'])
66         if not file_size:
67Index: django/core/files/uploadedfile.py
68===================================================================
69--- django/core/files/uploadedfile.py   (revision 7857)
70+++ django/core/files/uploadedfile.py   (working copy)
71@@ -3,13 +3,18 @@
72 """
73 
74 import os
75+import tempfile
76+import warnings
77 try:
78     from cStringIO import StringIO
79 except ImportError:
80     from StringIO import StringIO
81 
82-__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile')
83+from django.conf import settings
84 
85+
86+__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile')
87+
88 class UploadedFile(object):
89     """
90     A abstract uploaded file (``TemporaryUploadedFile`` and
91@@ -20,34 +25,87 @@
92     """
93     DEFAULT_CHUNK_SIZE = 64 * 2**10
94 
95-    def __init__(self, file_name=None, content_type=None, file_size=None, charset=None):
96-        self.file_name = file_name
97-        self.file_size = file_size
98+    def __init__(self, name=None, content_type=None, size=None, charset=None):
99+        self.name = name
100+        self.size = size
101         self.content_type = content_type
102         self.charset = charset
103 
104     def __repr__(self):
105-        return "<%s: %s (%s)>" % (self.__class__.__name__, self.file_name, self.content_type)
106+        return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)
107 
108-    def _set_file_name(self, name):
109+    def _get_name(self):
110+        return self._name
111+
112+    def _set_name(self, name):
113         # Sanitize the file name so that it can't be dangerous.
114         if name is not None:
115             # Just use the basename of the file -- anything else is dangerous.
116             name = os.path.basename(name)
117-           
118+
119             # File names longer than 255 characters can cause problems on older OSes.
120             if len(name) > 255:
121                 name, ext = os.path.splitext(name)
122                 name = name[:255 - len(ext)] + ext
123-               
124-        self._file_name = name
125-       
126+
127+        self._name = name
128+
129+    name = property(_get_name, _set_name)
130+
131+
132     def _get_file_name(self):
133-        return self._file_name
134-       
135-    file_name = property(_get_file_name, _set_file_name)
136+        warnings.warn(
137+            message = "To access the file name, please use the .name attribute.",
138+            category = DeprecationWarning,
139+            stacklevel = 2
140+            )
141+        return self.name
142 
143-    def chunk(self, chunk_size=None):
144+    def _set_file_name(self, name):
145+        warnings.warn(
146+            message = "To access the file name, please use the .name attribute.",
147+            category = DeprecationWarning,
148+            stacklevel = 2
149+            )
150+        self.name = name
151+
152+    file_name = filename = property(_get_file_name, _set_file_name)
153+
154+
155+    def _get_file_size(self):
156+        warnings.warn(
157+            message = "To access the file size, please use the .size attribute.",
158+            category = DeprecationWarning,
159+            stacklevel = 2
160+            )
161+        return self.size
162+
163+    def _set_file_size(self, size):
164+        warnings.warn(
165+            message = "To access the file size, please use the .size attribute.",
166+            category = DeprecationWarning,
167+            stacklevel = 2
168+            )
169+        self.size = size
170+
171+    file_size = property(_get_file_size, _set_file_size)
172+
173+
174+    def _get_data(self):
175+        warnings.warn(
176+            message = "To access the data, please use the new UploadedFile API.",
177+            category = DeprecationWarning,
178+            stacklevel = 2
179+            )
180+        data = self.read()
181+        if hasattr(self, 'seek'):
182+            self.seek(0)
183+        return data
184+
185+    data = property(_get_data)
186+
187+
188+    def chunks(self, chunk_size=None):
189         """
190         Read the file and yield chucks of ``chunk_size`` bytes (defaults to
191         ``UploadedFile.DEFAULT_CHUNK_SIZE``).
192@@ -58,12 +116,21 @@
193         if hasattr(self, 'seek'):
194             self.seek(0)
195         # Assume the pointer is at zero...
196-        counter = self.file_size
197+        counter = self.size
198 
199         while counter > 0:
200             yield self.read(chunk_size)
201             counter -= chunk_size
202 
203+    def chunk(self, *args, **kwargs):
204+        warnings.warn(
205+            message = "Please use .chunks() instead of .chunk().",
206+            category = DeprecationWarning,
207+            stacklevel = 2
208+            )
209+        for chunk in self.chunks():
210+            yield chunk
211+
212     def multiple_chunks(self, chunk_size=None):
213         """
214         Returns ``True`` if you can expect multiple chunks.
215@@ -74,9 +141,9 @@
216         """
217         if not chunk_size:
218             chunk_size = UploadedFile.DEFAULT_CHUNK_SIZE
219-        return self.file_size < chunk_size
220+        return self.size > chunk_size
221 
222-    # Abstract methods; subclasses *must* default read() and probably should
223+    # Abstract methods; subclasses *must* define read() and probably should
224     # define open/close.
225     def read(self, num_bytes=None):
226         raise NotImplementedError()
227@@ -87,60 +154,96 @@
228     def close(self):
229         pass
230 
231+    def xreadlines(self):
232+        return self
233+
234+    def readlines(self):
235+        return list(self.xreadlines())
236+
237+    def __iter__(self):
238+        # Iterate over this file-like object by newlines
239+        buffer_ = None
240+        for chunk in self.chunks():
241+            chunk_buffer = StringIO(chunk)
242+
243+            for line in chunk_buffer:
244+                if buffer_:
245+                    line = buffer_ + line
246+                    buffer_ = None
247+
248+                # If this is the end of a line, yield
249+                # otherwise, wait for the next round
250+                if line[-1] in ('\n', '\r'):
251+                    yield line
252+                else:
253+                    buffer_ = line
254+
255+        if buffer_ is not None:
256+            yield buffer_
257+
258     # Backwards-compatible support for uploaded-files-as-dictionaries.
259     def __getitem__(self, key):
260-        import warnings
261         warnings.warn(
262             message = "The dictionary access of uploaded file objects is deprecated. Use the new object interface instead.",
263             category = DeprecationWarning,
264             stacklevel = 2
265         )
266         backwards_translate = {
267-            'filename': 'file_name',
268+            'filename': 'name',
269             'content-type': 'content_type',
270             }
271 
272         if key == 'content':
273             return self.read()
274         elif key == 'filename':
275-            return self.file_name
276+            return self.name
277         elif key == 'content-type':
278             return self.content_type
279         else:
280             return getattr(self, key)
281 
282+
283+def _proxy_to_file(method_name):
284+    # Function factory to build proxy methods for the _file object.
285+    def __inner_function(self, *args, **kwargs):
286+        return getattr(self._file, method_name)(*args, **kwargs)
287+    __inner_function.__name__ = method_name
288+    return __inner_function
289+
290+
291 class TemporaryUploadedFile(UploadedFile):
292     """
293     A file uploaded to a temporary location (i.e. stream-to-disk).
294     """
295+    def __init__(self, name, content_type, size, charset):
296+        super(TemporaryUploadedFile, self).__init__(name, content_type, size, charset)
297+        if settings.FILE_UPLOAD_TEMP_DIR:
298+            self._file = tempfile.NamedTemporaryFile(suffix='.upload', dir=settings.FILE_UPLOAD_TEMP_DIR)
299+        else:
300+            self._file = tempfile.NamedTemporaryFile(suffix='.upload')
301 
302-    def __init__(self, file, file_name, content_type, file_size, charset):
303-        super(TemporaryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
304-        self.file = file
305-        self.path = file.name
306-        self.file.seek(0)
307-
308     def temporary_file_path(self):
309         """
310         Returns the full path of this file.
311         """
312-        return self.path
313+        return self.name
314 
315-    def read(self, *args, **kwargs):
316-        return self.file.read(*args, **kwargs)
317+    # Functions from the local file object
318+    read = _proxy_to_file('read')
319+    seek = _proxy_to_file('seek')
320+    write = _proxy_to_file('write')
321+    close = _proxy_to_file('close')
322+    __iter__ = _proxy_to_file('__iter__')
323+    readlines = _proxy_to_file('readlines')
324+    xreadlines = _proxy_to_file('xreadlines')
325 
326-    def open(self):
327-        self.seek(0)
328 
329-    def seek(self, *args, **kwargs):
330-        self.file.seek(*args, **kwargs)
331-
332 class InMemoryUploadedFile(UploadedFile):
333     """
334     A file uploaded into memory (i.e. stream-to-memory).
335     """
336-    def __init__(self, file, field_name, file_name, content_type, file_size, charset):
337-        super(InMemoryUploadedFile, self).__init__(file_name, content_type, file_size, charset)
338+    def __init__(self, file, field_name, name, content_type, size, charset):
339+        super(InMemoryUploadedFile, self).__init__(name, content_type, size, charset)
340         self.file = file
341         self.field_name = field_name
342         self.file.seek(0)
343@@ -154,7 +257,7 @@
344     def read(self, *args, **kwargs):
345         return self.file.read(*args, **kwargs)
346 
347-    def chunk(self, chunk_size=None):
348+    def chunks(self, chunk_size=None):
349         self.file.seek(0)
350         yield self.read()
351 
352@@ -168,9 +271,9 @@
353     """
354     def __init__(self, name, content, content_type='text/plain'):
355         self.file = StringIO(content or '')
356-        self.file_name = name
357+        self.name = name
358         self.field_name = None
359-        self.file_size = len(content or '')
360+        self.size = len(content or '')
361         self.content_type = content_type
362         self.charset = None
363         self.file.seek(0)
364Index: django/core/files/uploadhandler.py
365===================================================================
366--- django/core/files/uploadhandler.py  (revision 7857)
367+++ django/core/files/uploadhandler.py  (working copy)
368@@ -132,21 +132,15 @@
369         Create the file object to append to as data is coming in.
370         """
371         super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
372-        self.file = TemporaryFile(settings.FILE_UPLOAD_TEMP_DIR)
373-        self.write = self.file.write
374+        self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset)
375 
376     def receive_data_chunk(self, raw_data, start):
377-        self.write(raw_data)
378+        self.file.write(raw_data)
379 
380     def file_complete(self, file_size):
381         self.file.seek(0)
382-        return TemporaryUploadedFile(
383-            file = self.file,
384-            file_name = self.file_name,
385-            content_type = self.content_type,
386-            file_size = file_size,
387-            charset = self.charset
388-        )
389+        self.file.size = file_size
390+        return self.file
391 
392 class MemoryFileUploadHandler(FileUploadHandler):
393     """
394@@ -189,38 +183,13 @@
395         return InMemoryUploadedFile(
396             file = self.file,
397             field_name = self.field_name,
398-            file_name = self.file_name,
399+            name = self.file_name,
400             content_type = self.content_type,
401-            file_size = file_size,
402+            size = file_size,
403             charset = self.charset
404         )
405 
406-class TemporaryFile(object):
407-    """
408-    A temporary file that tries to delete itself when garbage collected.
409-    """
410-    def __init__(self, dir):
411-        if not dir:
412-            dir = tempfile.gettempdir()
413-        try:
414-            (fd, name) = tempfile.mkstemp(suffix='.upload', dir=dir)
415-            self.file = os.fdopen(fd, 'w+b')
416-        except (OSError, IOError):
417-            raise OSError("Could not create temporary file for uploading, have you set settings.FILE_UPLOAD_TEMP_DIR correctly?")
418-        self.name = name
419 
420-    def __getattr__(self, name):
421-        a = getattr(self.__dict__['file'], name)
422-        if type(a) != type(0):
423-            setattr(self, name, a)
424-        return a
425-
426-    def __del__(self):
427-        try:
428-            os.unlink(self.name)
429-        except OSError:
430-            pass
431-
432 def load_handler(path, *args, **kwargs):
433     """
434     Given a path to a handler, return an instance of that handler.
435Index: django/newforms/fields.py
436===================================================================
437--- django/newforms/fields.py   (revision 7857)
438+++ django/newforms/fields.py   (working copy)
439@@ -27,8 +27,8 @@
440 
441 from util import ErrorList, ValidationError
442 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
443+from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
444 
445-
446 __all__ = (
447     'Field', 'CharField', 'IntegerField',
448     'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
449@@ -419,19 +419,7 @@
450     # It's OK if Django settings aren't configured.
451     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
452 
453-class UploadedFile(StrAndUnicode):
454-    "A wrapper for files uploaded in a FileField"
455-    def __init__(self, filename, data):
456-        self.filename = filename
457-        self.data = data
458 
459-    def __unicode__(self):
460-        """
461-        The unicode representation is the filename, so that the pre-database-insertion
462-        logic can use UploadedFile objects
463-        """
464-        return self.filename
465-
466 class FileField(Field):
467     widget = FileInput
468     default_error_messages = {
469@@ -460,23 +448,20 @@
470                 category = DeprecationWarning,
471                 stacklevel = 2
472             )
473+            data = UploadedFile(data['filename'], data['content'])
474 
475         try:
476-            file_name = data.file_name
477-            file_size = data.file_size
478+            file_name = data.name
479+            file_size = data.size
480         except AttributeError:
481-            try:
482-                file_name = data.get('filename')
483-                file_size = bool(data['content'])
484-            except (AttributeError, KeyError):
485-                raise ValidationError(self.error_messages['invalid'])
486+            raise ValidationError(self.error_messages['invalid'])
487 
488         if not file_name:
489             raise ValidationError(self.error_messages['invalid'])
490         if not file_size:
491             raise ValidationError(self.error_messages['empty'])
492 
493-        return UploadedFile(file_name, data)
494+        return data
495 
496 class ImageField(FileField):
497     default_error_messages = {
498Index: tests/modeltests/model_forms/models.py
499===================================================================
500--- tests/modeltests/model_forms/models.py      (revision 7857)
501+++ tests/modeltests/model_forms/models.py      (working copy)
502@@ -803,7 +803,7 @@
503 >>> f.is_valid()
504 True
505 >>> type(f.cleaned_data['file'])
506-<class 'django.newforms.fields.UploadedFile'>
507+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
508 >>> instance = f.save()
509 >>> instance.file
510 u'...test1.txt'
511@@ -814,7 +814,7 @@
512 >>> f.is_valid()
513 True
514 >>> type(f.cleaned_data['file'])
515-<class 'django.newforms.fields.UploadedFile'>
516+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
517 >>> instance = f.save()
518 >>> instance.file
519 u'...test1.txt'
520@@ -906,7 +906,7 @@
521 >>> f.is_valid()
522 True
523 >>> type(f.cleaned_data['image'])
524-<class 'django.newforms.fields.UploadedFile'>
525+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
526 >>> instance = f.save()
527 >>> instance.image
528 u'...test.png'
529@@ -918,7 +918,7 @@
530 >>> f.is_valid()
531 True
532 >>> type(f.cleaned_data['image'])
533-<class 'django.newforms.fields.UploadedFile'>
534+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
535 >>> instance = f.save()
536 >>> instance.image
537 u'...test.png'
538Index: tests/regressiontests/forms/fields.py
539===================================================================
540--- tests/regressiontests/forms/fields.py       (revision 7857)
541+++ tests/regressiontests/forms/fields.py       (working copy)
542@@ -800,10 +800,10 @@
543 ValidationError: [u'The submitted file is empty.']
544 
545 >>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
546-<class 'django.newforms.fields.UploadedFile'>
547+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
548 
549 >>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
550-<class 'django.newforms.fields.UploadedFile'>
551+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
552 
553 # URLField ##################################################################
554 
555Index: docs/upload_handling.txt
556===================================================================
557--- docs/upload_handling.txt    (revision 7857)
558+++ docs/upload_handling.txt    (working copy)
559@@ -22,7 +22,7 @@
560     class UploadFileForm(forms.Form):
561         title = forms.CharField(max_length=50)
562         file  = forms.FileField()
563-       
564+
565 A view handling this form will receive the file data in ``request.FILES``, which
566 is a dictionary containing a key for each ``FileField`` (or ``ImageField``, or
567 other ``FileField`` subclass) in the form. So the data from the above form would
568@@ -64,32 +64,32 @@
569     ``UploadedFile.read()``
570         Read the entire uploaded data from the file. Be careful with this
571         method: if the uploaded file is huge it can overwhelm your system if you
572-        try to read it into memory. You'll probably want to use ``chunk()``
573+        try to read it into memory. You'll probably want to use ``chunks()``
574         instead; see below.
575-       
576+
577     ``UploadedFile.multiple_chunks()``
578         Returns ``True`` if the uploaded file is big enough to require
579         reading in multiple chunks. By default this will be any file
580         larger than 2.5 megabytes, but that's configurable; see below.
581-   
582+
583     ``UploadedFile.chunk()``
584         A generator returning chunks of the file. If ``multiple_chunks()`` is
585         ``True``, you should use this method in a loop instead of ``read()``.
586-       
587+
588         In practice, it's often easiest simply to use ``chunks()`` all the time;
589         see the example below.
590-   
591+
592     ``UploadedFile.file_name``
593         The name of the uploaded file (e.g. ``my_file.txt``).
594-       
595+
596     ``UploadedFile.file_size``
597         The size, in bytes, of the uploaded file.
598-       
599+
600 There are a few other methods and attributes available on ``UploadedFile``
601 objects; see `UploadedFile objects`_ for a complete reference.
602 
603 Putting it all together, here's a common way you might handle an uploaded file::
604-   
605+
606     def handle_uploaded_file(f):
607         destination = open('some/file/name.txt', 'wb')
608         for chunk in f.chunks():
609@@ -126,27 +126,27 @@
610         The maximum size, in bytes, for files that will be uploaded
611         into memory. Files larger than ``FILE_UPLOAD_MAX_MEMORY_SIZE``
612         will be streamed to disk.
613-       
614+
615         Defaults to 2.5 megabytes.
616-       
617+
618     ``FILE_UPLOAD_TEMP_DIR``
619         The directory where uploaded files larger than ``FILE_UPLOAD_TEMP_DIR``
620         will be stored.
621-       
622+
623         Defaults to your system's standard temporary directory (i.e. ``/tmp`` on
624         most Unix-like systems).
625-       
626+
627     ``FILE_UPLOAD_HANDLERS``
628         The actual handlers for uploaded files. Changing this setting
629         allows complete customization -- even replacement -- of
630         Django's upload process. See `upload handlers`_, below,
631         for details.
632-       
633+
634         Defaults to::
635-       
636+
637             ("django.core.files.uploadhandler.MemoryFileUploadHandler",
638              "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
639-           
640+
641         Which means "try to upload to memory first, then fall back to temporary
642         files."
643 
644@@ -161,35 +161,39 @@
645         Returns a byte string of length ``num_bytes``, or the complete file if
646         ``num_bytes`` is ``None``.
647 
648-    ``UploadedFile.chunk(self, chunk_size=None)``
649+    ``UploadedFile.chunks(self, chunk_size=None)``
650         A generator yielding small chunks from the file. If ``chunk_size`` isn't
651-        given, chunks will be 64 kb.
652+        given, chunks will be 64 KB.
653 
654     ``UploadedFile.multiple_chunks(self, chunk_size=None)``
655         Returns ``True`` if you can expect more than one chunk when calling
656-        ``UploadedFile.chunk(self, chunk_size)``.
657+        ``UploadedFile.chunks(self, chunk_size)``.
658 
659     ``UploadedFile.file_size``
660         The size, in bytes, of the uploaded file.
661-   
662+
663     ``UploadedFile.file_name``
664         The name of the uploaded file as provided by the user.
665-   
666+
667     ``UploadedFile.content_type``
668         The content-type header uploaded with the file (e.g. ``text/plain`` or
669         ``application/pdf``). Like any data supplied by the user, you shouldn't
670         trust that the uploaded file is actually this type. You'll still need to
671         validate that the file contains the content that the content-type header
672         claims -- "trust but verify."
673-   
674+
675     ``UploadedFile.charset``
676         For ``text/*`` content-types, the character set (i.e. ``utf8``) supplied
677         by the browser. Again, "trust but verify" is the best policy here.
678 
679+    ``UploadedFile.__iter__()``
680+        Iterates over the lines in the file.
681+
682     ``UploadedFile.temporary_file_path()``
683         Only files uploaded onto disk will have this method; it returns the full
684         path to the temporary uploaded file.
685 
686+
687 Upload Handlers
688 ===============
689 
690Index: docs/newforms.txt
691===================================================================
692--- docs/newforms.txt   (revision 7857)
693+++ docs/newforms.txt   (working copy)
694@@ -1331,23 +1331,12 @@
695     * Validates that non-empty file data has been bound to the form.
696     * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``
697 
698-An ``UploadedFile`` object has two attributes:
699+To learn more about the ``UploadedFile`` object, see the `file uploads documentation`_.
700 
701-    ======================  ====================================================
702-    Attribute               Description
703-    ======================  ====================================================
704-    ``filename``            The name of the file, provided by the uploading
705-                            client.
706-                           
707-    ``content``             The array of bytes comprising the file content.
708-    ======================  ====================================================
709-
710-The string representation of an ``UploadedFile`` is the same as the filename
711-attribute.
712-
713 When you use a ``FileField`` in a form, you must also remember to
714 `bind the file data to the form`_.
715 
716+.. _file uploads documentation: ../upload_handling/
717 .. _`bind the file data to the form`: `Binding uploaded files to a form`_
718 
719 ``FilePathField``
720Index: docs/settings.txt
721===================================================================
722--- docs/settings.txt   (revision 7857)
723+++ docs/settings.txt   (working copy)
724@@ -279,7 +279,7 @@
725 
726 The database backend to use. The build-in database backends are
727 ``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
728-``'sqlite3'``, ``'oracle'``, and ``'oracle'``.
729+``'sqlite3'``, and ``'oracle'``.
730 
731 In the Django development version, you can use a database backend that doesn't
732 ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.