Anonymous avatar Anonymous committed 397c8c6

Fixed #10044: You can now assign directly to file fields (`instance.filefield = somefile`). Thanks, Marty Alchin.

Comments (0)

Files changed (3)

django/core/files/base.py

         return self.size
 
     def _get_name(self):
+        if not hasattr(self, '_name'):
+            raise ValueError("This operation requires the file to have a name.")
         return self._name
     name = property(_get_name)
 

django/db/models/fields/files.py

+import copy
 import datetime
 import os
 
         self.storage = field.storage
         self._name = name or u''
         self._closed = False
+        self._committed = True
 
     def __eq__(self, other):
         # Older code may be expecting FileField values to be simple strings.
 
         # Update the filesize cache
         self._size = len(content)
+        self._committed = True
 
         # Save the object because it has changed, unless save is False
         if save:
         # Delete the filesize cache
         if hasattr(self, '_size'):
             del self._size
+        self._committed = False
 
         if save:
             self.instance.save()
         # it's attached to in order to work properly, but the only necessary
         # data to be pickled is the file's name itself. Everything else will
         # be restored later, by FileDescriptor below.
-        return {'_name': self.name, '_closed': False}
+        return {'_name': self.name, '_closed': False, '_committed': True}
 
 class FileDescriptor(object):
     def __init__(self, field):
         if instance is None:
             raise AttributeError, "%s can only be accessed from %s instances." % (self.field.name(self.owner.__name__))
         file = instance.__dict__[self.field.name]
-        if not isinstance(file, FieldFile):
+        if isinstance(file, basestring) or file is None:
             # Create a new instance of FieldFile, based on a given file name
             instance.__dict__[self.field.name] = self.field.attr_class(instance, self.field, file)
-        elif not hasattr(file, 'field'):
+        elif isinstance(file, File) and not isinstance(file, FieldFile):
+            # Other types of files may be assigned as well, but they need to
+            # have the FieldFile interface added to them
+            file_copy = copy.copy(file)
+            file_copy.__class__ = type(file.__class__.__name__, 
+                                       (file.__class__, FieldFile), {})
+            file_copy.instance = instance
+            file_copy.field = self.field
+            file_copy.storage = self.field.storage
+            file_copy._committed = False
+            instance.__dict__[self.field.name] = file_copy
+        elif isinstance(file, FieldFile) and not hasattr(file, 'field'):
             # The FieldFile was pickled, so some attributes need to be reset.
             file.instance = instance
             file.field = self.field
             return None
         return unicode(value)
 
+    def pre_save(self, model_instance, add):
+        "Returns field's value just before saving."
+        file = super(FileField, self).pre_save(model_instance, add)
+        if file and not file._committed:
+            # Commit the file to storage prior to saving the model
+            file.save(file.name, file, save=False)
+        return file
+
     def contribute_to_class(self, cls, name):
         super(FileField, self).contribute_to_class(cls, name)
         setattr(cls, self.name, FileDescriptor(self))
     def generate_filename(self, instance, filename):
         return os.path.join(self.get_directory_name(), self.get_filename(filename))
 
-    def save_form_data(self, instance, data):
-        if data and isinstance(data, UploadedFile):
-            getattr(instance, self.name).save(data.name, data, save=False)
-
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.FileField}
         # If a file has been provided previously, then the form doesn't require

tests/modeltests/files/models.py

 import tempfile
 from django.db import models
 from django.core.files.base import ContentFile
+from django.core.files.uploadedfile import SimpleUploadedFile
 from django.core.files.storage import FileSystemStorage
 from django.core.cache import cache
 
 >>> obj1.normal.read()
 'content'
 
+# File objects can be assigned to FileField attributes,  but shouldn't get
+# committed until the model it's attached to is saved.
+
+>>> obj1.normal = SimpleUploadedFile('assignment.txt', 'content')
+>>> dirs, files = temp_storage.listdir('tests')
+>>> dirs
+[]
+>>> files.sort()
+>>> files
+[u'default.txt', u'django_test.txt']
+
+>>> obj1.save()
+>>> dirs, files = temp_storage.listdir('tests')
+>>> files.sort()
+>>> files
+[u'assignment.txt', u'default.txt', u'django_test.txt']
+
 # Files can be read in a little at a time, if necessary.
 
 >>> obj1.normal.open()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.