2

I am trying to specify a specific method of handling file uploads for a class based view. Per the docs this can be achieved by something like:

from django.core.files.uploadhandler import TemporaryFileUploadHandler
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]

If i specify this in post method of a FormView like so:

def post(self, request, *args, **kwargs):
    request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
    return super().post(self,  request, *args, **kwargs)

I get:

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

Variants like yield the same result:

def post(self, request, *args, **kwargs):
    self.request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
    form = self.get_form()
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

However when i do this in the get method this is ineffective:

 def get(self, request, *args, **kwargs):
    request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]  
    return super().get(self,  request, *args, **kwargs)

If I upload a small file it still uses the default django.core.files.uploadhandler.MemoryFileUploadHandler.

What am I doing wrong?

EDIT

Also when i try to mirror what is suggested in the note, I get the same AttributeError:

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def post(self, request, *args, **kwargs):
    request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
    return self._post(request, *args, **kwargs)

@csrf_protect
def _post(self, request, *args, **kwargs):
    form = self.get_form()
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)
BartDur
  • 1,086
  • 1
  • 12
  • 21
  • 1
    Did you mark the view as `csrf_exempt`, as explained in the [note in the docs](https://docs.djangoproject.com/en/1.11/topics/http/file-uploads/#id1). If not, then the CSRF protection will read `request.POST` before you get a chance to modify the upload handlers. – Alasdair Nov 22 '17 at 16:40
  • Thanks for the tip! Unfortunately when I apply the pattern as suggested in the note, I still get the same error. However, could it be possible that some other middleware messes with the request before it reaches my view? – BartDur Nov 22 '17 at 17:07
  • 1
    Perhaps you are using `csrf_exempt` incorrectly, or perhaps it could be some other middleware as you suggested. – Alasdair Nov 22 '17 at 17:13
  • I expanded the question to show you how I applied it. Thanks for your suggestions, it helps a lot for my understanding – BartDur Nov 22 '17 at 17:22
  • 2
    To decorate a [class based view](https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/#decorating-the-class) you need to use `method_decorator`. I recommend that you do some testing to make sure that the csrf protection still works after you have made the changes -- you don't want it to fail silently. Remove the `{% crsf_token %}` tag from your template, and check that you get a CSRF error for POST requests. – Alasdair Nov 22 '17 at 17:30

2 Answers2

4

Ok, finally got it to work (using the suggestions provided by @Alasdair). Setting a method decorator(crsf_exempt) on post is not engough it needs to be on dispatch. For anyone struggling with this in the future, it goes like this:

from django.views.generic import FormView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect


@method_decorator(csrf_exempt, 'dispatch')
class UploadDataSetView(FormView):

    def post(self, request, *args, **kwargs):
        request.upload_handlers = [TemporaryFileUploadHandler(request=request)]  
        return self._post(request)

    @method_decorator(csrf_protect)
    def _post(self, request):
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

Also it will fail if you remove the {% csrf_token %} from your template (which is what you want).

BartDur
  • 1,086
  • 1
  • 12
  • 21
  • When I upload a large file, it takes a long time to get into the view function. Was the uploaded file saved somewhere on the server before passing through FileUploadHandle?I want to customize the handling of the uploaded files, such as storing the file stream directly to another server. – gao.xiangyang May 08 '21 at 02:11
  • I'm having the same issue, but I dread the idea of making it csrf_exempt. I guess that just might be the only thing to do, though. I'm processing a whole model form along with the file upload, so I'm nervous of a security issue if I make it CSRF exempt. – Bobort Oct 04 '21 at 19:44
2

Because you cannot change upload handler in view as it is something that gets invoked prior to your view function.

Get shouldn't collect post parameters so it behaves accordingly.

Upload Handlers

When a user uploads a file, Django passes off the file data to an upload handler – a small class that handles file data as it gets uploaded. Upload handlers are initially defined in the FILE_UPLOAD_HANDLERS setting, which defaults to:

 ["django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler"]

If you want different upload handler you can change FILE_UPLOAD_HANDLERS settings

If this is not enough you can write your custom upload handler

Edit:

Also, request.POST is accessed byCsrfViewMiddleware which is enabled by default. This means you will need to use csrf_exempt() on your view to allow you to change the upload handlers.

iklinac
  • 14,944
  • 4
  • 28
  • 30
  • indeed the `get` approach was born out of desperation... however, as i refer to in my post the docs say that you can replace the default behavior for a specific view, that's what i am after. From [Modifying upload handlers on the fly](https://docs.djangoproject.com/en/1.11/topics/http/file-uploads/#s-id1): "Sometimes particular views require different upload behavior. In these cases, you can override upload handlers on a per-request basis..." – BartDur Nov 22 '17 at 15:49
  • 1
    Documentation also notes: **You can only modify upload handlers before accessing request.POST or request.FILES – it doesn’t make sense to change upload handlers after upload handling has already started. If you try to modify request.upload_handlers after reading from request.POST or request.FILES Django will throw an error.** You must change upload behavior before post. – Borut Nov 22 '17 at 16:25
  • @borut yes you are right, check edit on my answer, csrf is responsible for accessing it prior – iklinac Nov 22 '17 at 16:51