#10300: Custom File Storage Backend broken by recent SVN commit.
-------------------------------------------+--------------------------------
          Reporter:  erikcw                |         Owner:  nobody             
   
            Status:  new                   |     Milestone:                     
   
         Component:  File uploads/storage  |       Version:  SVN                
   
        Resolution:                        |      Keywords:  file upload 
storage s3
             Stage:  Accepted              |     Has_patch:  0                  
   
        Needs_docs:  0                     |   Needs_tests:  0                  
   
Needs_better_patch:  0                     |  
-------------------------------------------+--------------------------------
Changes (by kmtracey):

  * stage:  Unreviewed => Accepted

Comment:

 I looked at this a little, though I cannot recreate since I do not have
 access to S3 (let alone the modified backend...the modifications may be
 relevant here).  However, taking the S3 backend you point to and stubbing
 it out to not actually attempt to call S3 services but just log what it's
 called to do, I see a difference pre- and post- r9766 involving calls to
 the backend's `size` method.  Specifically, prior to r9766 the S3Storage
 `size` method is not called when a file is uploaded (via admim).  Running
 with r9766, however, it is called.  Ultimately the call comes from the
 len(content) call in the !FieldFile save method in
 django/db/models/fields/files.py:

 {{{
 #!python
     def save(self, name, content, save=True):
         name = self.field.generate_filename(self.instance, name)
         self._name = self.storage.save(name, content)
         setattr(self.instance, self.field.name, self.name)

         # Update the filesize cache
         print 'Prior to updating filesize cache:
 content.__class__.__bases__ is: %s' % str(content.__class__.__bases__)
         self._size = len(content)

         # Save the object because it has changed, unless save is False
         if save:
             self.instance.save()
     save.alters_data = True
 }}}

 Though this method itself did not change, that print (added by me for
 debug) shows that the bases for content has changed.  Prior to r9766 it
 prints:

 {{{
 Prior to updating filesize cache: content.__class__.__bases__ is: (<class
 'django.core.files.uploadedfile.UploadedFile'>,)
 }}}

 Post-r9766 it prints:

 {{{
 Prior to updating filesize cache: content.__class__.__bases__ is: (<class
 'django.core.files.uploadedfile.InMemoryUploadedFile'>, <class
 django.db.models.fields.files.FieldFile'>)
 }}}

 The effect of this difference is that instead of django/core/files/base.py
 File's _get_size:

 {{{
 #!python
     def _get_size(self):
         if not hasattr(self, '_size'):
             if hasattr(self.file, 'size'):
                 self._size = self.file.size
             elif os.path.exists(self.file.name):
                 self._size = os.path.getsize(self.file.name)
             else:
                 raise AttributeError("Unable to determine the file's
 size.")
         return self._size

     def _set_size(self, size):
         self._size = size

     size = property(_get_size, _set_size)
 }}}

 being called to determine len(content) (with _size having already been set
 previously at a point in the code I didn't track down),
 django/db/models/fields/files.py !FieldFile's _get_size:

 {{{
 #!python
     def _get_size(self):
         self._require_file()
         return self.storage.size(self.name)
     size = property(_get_size)
 }}}

 is called instead.

 Now the `_save` method in the S3 storage backend you point to does not
 rely on len(content) when it saves the data, but if your modified version
 does then it likely has a problem.  If I put some prints into `_save`:

 {{{
 #!python
         print 'in _save: type(content) = %s, content.__class__.__bases__
 is: %s' % (str(type(content)), str(content.__class__.__bases__))
         print 'in _save: len(content): %d' % len(content)
 }}}

 Prior to r9766 I get:

 {{{
 in _save: type(content) = <class
 'django.core.files.uploadedfile.InMemoryUploadedFile'>,
 content.__class__.__bases__ is: (<class
 'django.core.files.uploadedfile.UploadedFile'>,)
 in _save: len(content): 818
 }}}

 Post r9766 I get:

 {{{
 in _save: type(content) = <class
 'django.db.models.fields.files.InMemoryUploadedFile'>,
 content.__class__.__bases__ is: (<class
 'django.core.files.uploadedfile.InMemoryUploadedFile'>, <class
 'django.db.models.fields.files.FieldFile'>
 }}}

 and my stubbed backend's `size` raises an exception because it is called
 to return the length of a file it hasn't saved yet.

 So it seems there is a fairly non-obvious side-effect of r9766 involving
 len(content) for the content passed into the backend `_save`:

  * Prior to r9766 this would resolve to django/core/files/base.py File's
 _get_size method

  * Post-r9766 the backend's own `size` method will get called, which isn't
 likely to work since `_save` itself hasn't had a chance to save the file
 yet.

 But I don't actually know for sure that this is the cause of the problem
 reported here, since I don't know that the modified backend relies on
 len(content) in its `_save` method.  It does seem to be a problem we need
 to fix, though, since it seems valid for a backend `_save` to call len on
 the passed content, and it used to work...now it will find itself called
 to answer the len() question, which likely isn't going to work.

 This is likely related to #10249...that one is reporting inability to
 determine a method resolution order while this one seems to be resulting
 from the fact that the method resolution order has changed.

 Feedback from someone with a better understanding of r9766 would be
 helpful here.

-- 
Ticket URL: <http://code.djangoproject.com/ticket/10300#comment:3>
Django <http://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to