1

I have a view in which I try to upload an excel sheet via a form. The excel that I upload isn't that big but the processing of the data in that excel is causing nginx time out issues resulting in a 502 Bad Gateway error. I do not have this problem when testing locally but it pops up on AWS EC2 when testing it on that environment.

I increased the time out periods in nginx via (as suggested here):

proxy_read_timeout 300s;
proxy_connect_timeout 300s;

proxy_buffer_size          128k;
proxy_buffers              4 256k;
proxy_busy_buffers_size    256k;

And that did improve things somewhat since I can see more results but it was not sufficient. Curiously it timed out again before the 300s were up. Incidentally, if it used all 300s it should complete the process.

It was also suggested I use async to make the processing independent of the upload process and that it run the processing in the background. However in attempting I keep running into this error:

"object HttpResponseRedirect can't be used in 'await' expression"

This error occurs when I try to load the page even before I try to upload the excel file which is the only time I have a redirect, which is meant to redirect to a page where the uploaded documents are listed.

If I remove the @sync_to_async decorator from the upload view I instead get this error

"didn't return an HttpResponse object. It returned an unawaited coroutine instead. You may need to add an 'await' into your view"

How can I resolve this?

@sync_to_async
def process_excel_upload(request, excel_file):
    ** processing of the excel document ***
    return ("complete")

@login_required
@sync_to_async
@user_preapproved(home_url='landing-dashboard', redirect_field_name='', message='You are not authorised to perform this action.')
@user_passes_test(group_check, redirect_field_name=None)
async def ExcelUploadEditView(request):
    user = request.user
    member = ProfileMember.objects.get(user=user)
    if request.method == 'POST':
        form = ExcelUploadForm(request.POST or None, request.FILES or None)
        if form.is_valid():            
            json_payload = {
                "message": "Your file is being processed"
            }
            excel = request.POST.get('excel')
            await asyncio.create_task(process_excel_upload(request,excel))
            form.save()
            return redirect('excel-after',json_payload)
        else:
            print(form.errors)
    else:
        form = ReportUploadForm()
        context = {
            'form_documentupload': form,
            'user': user,
            'member':member,

            }
        return render(request, "excel_upload.html", context)
4
  • 1
    I see my answer worked for you. However I want to make some clarifications: My code only allows one upload per user at the time. You should add some checks in case a user tries to make an upload while another upload is happening cause this will mess up with the UploadStatus model. If you want that a user is able to upload again before the previous uploads are complete you should: remove user as primary key so an id field is added to the model, pass the id when you are asking for the state of the upload, and clean UploadStatus from when to when so it doesn't get loaded with previous uploads. Commented Dec 16, 2023 at 18:32
  • @42WaysToAnswerThat I did make some of those changes. What I did notice though is that the thread doesn't seem to be able to call other functions. Is that normal? Prior to this, as it processed each item it would send an email upon its success. Now the print function before that is called prints correctly but the send_mail function isn't triggered. The prints in that function aren't triggered either It is clear to me that that function isn't being called but I see no errors in the logs. It essentially failed silently. Is that expected? Not very familiar with threading so wondering if it's a li
    – fmakawa
    Commented Dec 16, 2023 at 18:58
  • If I understand correctly, the functions inside the thread are not being called properly. I'm not sure what is the problem since I've only ever used threads for simple tasks. However, you may try passing the functions you want to call as arguments to the thread function: def process_excel_upload(request, excel_file, **args) then call the function using its index: args[<indx>](<arguments>). If this doesn't work try opening a new question and share the problematic code, so I and other people can check it for solutions. Commented Dec 16, 2023 at 19:38
  • turns out it was an authentication issue So all good. Microsft have changed their default settings and that was resulting in the rest of the code not being processed.
    – fmakawa
    Commented Dec 19, 2023 at 19:34

1 Answer 1

1
+50

This might work for you:

[views.py]

    import threading
    
    def process_excel_upload(request, excel_file):
        ** processing of the excel document ***
        upload_status = UploadStatus.objects.get(user=request.user)
        upload_status.status = 'complete'
        upload_status.save()
    
    def get_upload_status(request):
        upload_status = UploadStatus.objects.get(user=request.user)
        return JsonResponse({'status': upload_status.status})
    
    @login_required
    @user_preapproved(home_url='landing-dashboard', redirect_field_name='', message='You are not authorised to perform this action.')
    @user_passes_test(group_check, redirect_field_name=None)
    def ExcelUploadEditView(request):
        user = request.user
        member = ProfileMember.objects.get(user=user)
        if request.method == 'POST':
            form = ExcelUploadForm(request.POST or None, request.FILES or None)
            if form.is_valid():            
                json_payload = {
                    "message": "Your file is being processed"
                }
                excel = request.POST.get('excel')
                upload_status = UploadStatus.objects.get(user=request.user)
                upload_status.status = 'processing'
                upload_status.save()
                t = threading.Thread(target=process_excel_upload,args=(request,excel))
                t.setDaemon(True)
                t.start()
                form.save()
                return redirect('excel-after',json_payload)
            else:
                print(form.errors)
        else:
            form = ReportUploadForm()
            context = {
                'form_documentupload': form,
                'user': user,
                'member':member,
                }
            return render(request, "excel_upload.html", context)

[template.html]

    //call this function in the upload response:
    
    function checkUploadStatus() {
        $.ajax({
            url: '/get_upload_status/',  // replace with the URL you set to your get_upload_status view
            success: function(data) {
                if (data.stats === 'complete') {
                    //replace the uploading... message with a completion message
                } else {
                    setTimeout(checkUploadStatus, 3000);
                }
            }
        });
    }

[models.py]

    from django.contrib.auth.models import User
    
    class UploadStatus(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
        status = models.CharField(max_length=20, default='pending')

You may also consider using web sockets, tho I believe is an overkill

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.