views:

176

answers:

1

So, I'm working on implementing the answer to my previous question.

Here's my model:

class Talk(models.Model):
  title        = models.CharField(max_length=200)
  mp3          = models.FileField(upload_to = u'talks/', max_length=200)

Here's my form:

class TalkForm(forms.ModelForm):
  def clean(self):
    super(TalkForm, self).clean()
    cleaned_data = self.cleaned_data

    if u'mp3' in self.files:
      from mutagen.mp3 import MP3
      if hasattr(self.files['mp3'], 'temporary_file_path'):
        audio = MP3(self.files['mp3'].temporary_file_path())
      else:
        # What goes here?
        audio = None # setting to None for now
      ...
    return cleaned_data

  class Meta:
    model = Talk

Mutagen needs file-like objects or filenames on disk (I think) - the first case (where the uploaded file is larger than the size of file handled in memory) works fine, but I don't know how to handle InMemoryUploadedFile that I get otherwise. I've tried:

# TypeError (coercing to Unicode: need string or buffer, InMemoryUploadedFile found)
audio = MP3(self.files['mp3'])

# TypeError (coercing to Unicode: need string or buffer, cStringIO.StringO found)
audio = MP3(self.files['mp3'].file)

# Hangs seemingly indefinitely on my test file (~800KB)
audio = MP3(self.files['mp3'].file.read())

Is there something wrong with mutagen, or am I doing it wrong?

After rebus' answer

Modifying the FILE_UPLOAD_HANDLERS setting on the fly in my ModelAdmin class like this:

def add_view(self, request, form_url='', extra_context=None):
  request.upload_handlers = [TemporaryFileUploadHandler()]
  return super(TalkAdmin, self).add_view(request, form_url, extra_context)

Gets me the following error 500 when I hit submit:

You cannot set the upload handlers after the upload has been processed.

even though I'm doing it as early as I possibly can!

Also, I'm not sure I've got a save method on the object I'm getting back (I've looked in dir(self.files['mp3'].file) and dir(self.files['mp3'])).

+2  A: 

You could try to change your FILE_UPLOAD_HANDLERS in such a way so Django always uses temporay file handler:

FILE_UPLOAD_HANDLERS default:

("django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

So you could leave only TemporaryFileUploadHandler by overriding the setting in your settings.py.

Edit:

Much simpler, should have thought of it at the first place :(:

from your.models import Talk
mp3 = self.files['mp3']
f = Talk.mp3.save('somename.mp3', mp3)
MP3(f.mp3.path)
>>> {'TRCK': TRCK(encoding=0, text=[u'5'])}

You can save InMemoryUploadedFile to the disk this way and then use the path to that file to work with mutagen.

Edit:

Same thing without a models instance.

import os

from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.conf import settings

from mutagen.mp3 import MP3

mp3 = request.FILES['mp3'] # or self.files['mp3'] in your form

path = default_storage.save('tmp/somename.mp3', ContentFile(mp3.read()))
MP3(os.path.join(settings.MEDIA_ROOT, path))

Note that it's saving the file in MEDIA_ROOT, when i try to save it anywhere else i get SuspiciousOperation since there are limits to where you can write... You should delete this file after examining it i guess, the real thing will be on your model...

path = default_storage.delete('tmp/somename.mp3')
rebus
That's a bit tricky in my scenario, since I don't really have access (or rather, don't want to monkey-path) the views concerned, which are the admin add object and change object views.
Dominic Rodger
Actually, you can subclass both views in your `ModelAdmin` definition for that model http://docs.djangoproject.com/en/dev/ref/contrib/admin/#other-methods
rebus
@rebus - thanks for your help, but no luck - see my edit. Any other ideas?
Dominic Rodger
@Dominic Rodger i've edited an answer a bit, this should be much simpler to do, it turns out that it not quite easy to get ahead of the csrf protection around admin views (which accesses the request.POST) to change the upload_handlers.
rebus
@rebus - excellent - thanks so much for helping me work this out! Haven't had a chance to try it yet, but I'll let you know once I have!
Dominic Rodger
@rebus - your edited solution won't work (you can't access Talk.mp3 except through an instance of Talk) - I've just gone and turned off MemoryFileUploadHandler - I'll take the hit on smaller file uploads for the sake of my sanity at this point.
Dominic Rodger
@Dominic Rodger didn't get notified about your comment. Heres one more example with using storage engine directly... apart from this i am stumped...
rebus
@rebus - I've started a bounty on this question, which I intend to award you once I'm allowed to say thank you for all your help. I'll have a go at the `default_storage` stuff later tonight, but if that doesn't work, I'm quite happy leaving `MemoryFileUploadhandler` turned off. Thanks again!
Dominic Rodger