0

I have a form which takes some user input, posts to a view, does some calculations and then redirects to the output.

While the calculations are running I can see the little wheel spinning in my browser tab, but the user has to look at their form just sitting there while they wait.

I would like to make a status view of sorts, where while the calculations are underway at certain parts it shows the user a message regarding its progress e.g. "at part 3 of the process", and when it is done then redirects to the results.

So I am asking for someone to help me do this.

e.g. in views.py

def form(request):
    if request.method == 'POST'
    form = MyForm(request.POST)
        if form.is_valid():
             form = convert_form_to_model(form.cleaned_data, MyModel)
             # DO calculations
             send_message_to_user_waiting_screen('Part One Complete')
             # DO more calculations
             send_message_to_user_waiting_screen('Part two Complete')
             ....
             return HttpResponseRedirect(reverse('my_app:results', args(form.id,)))
        else:
            return render(request, 'my_app/form.html')
    else:
        form = MyForm()
        render(request, 'my_app/form.html')
Dhia
  • 10,119
  • 11
  • 58
  • 69
SumNeuron
  • 4,850
  • 5
  • 39
  • 107

3 Answers3

2

I've given you enough to get started, but you will have to learn some jquery and javascript on your own to handle the messages you're getting from polling. It's not that bad; there are numerous examples you can use on stack overflow and all over the internet. Hope this helps.

add to models:

class UserCalculationUpdate(models.Model)
    user = models.ForeignKey(User)
    stage = models.SmallIntegerField(default=0, max_length=1)
    message = models.TextField(default='')

    _stage_dict = {-1: 'Failed.', 0: 'On stage 1 of 3', 1: 'On stage 2 of 3', 
                    2: 'On stage 3 of 3', 3: 'Calculation Complete!'}

    def get_stage(self):
        return self._stage_dict[self.stage]

run python manage.py makemigrations, debug any mistakes I've made, and run python manage.py migrate.

import your new model and json stuff and edit your view:

from django.http import JsonResponse

from my_app.models import UserCalculationUpdate 

# I'd rename your view to something else.
def form(request):
    if request.method == 'POST'
    form = MyForm(request.POST)
        if form.is_valid():
            form = convert_form_to_model(form.cleaned_data, MyModel)
             """
             looks to see if there is a UserCalculationUpdate object 
             corresponding to the present user. If not, it creates one
             and sets obj.stage = 0 and sets created = True, otherwise, 
             it sets obj.stage = 0 for the object already in the db 
             and sets created = False
             """
            obj, created = UserCalculationUpdate.objects \
                .update_or_create(user=request.user, 
                                  defaults={'stage':0}) 
            result1, success, message = step_one_calc(form)
            # storing the progress in our new model, success should be
            # a boolean
            if success:
                obj.update(stage=1)
            else obj.update(stage=-1, message=message)
                return HttpResponse() # whatever you want to happen when fails
            result2, success, message = step_two_calc(result1, success)
            if success: 
                obj.update(stage=2)
            else obj.update(stage=-1, 
                            message=message)
                return HttpResponse() # whatever you want to happen when fails
            """
            . . . and so on . . .
            """ 
            return HttpResponseRedirect(reverse('my_app:results', args(form.id,)))
        else:
            return render(request, 'my_app/form.html')
    else:
        form = MyForm()
        render(request, 'my_app/form.html')

def poll_view(request):
    user_calc_update = UserCalculationUpdate.objects \
        .filter(user=request.user):
    if len(user_calc_update) != 0:
        stage_message = user_calc_update[0].get_stage()
        results_message = user_calc_update[0].message
        # 0 is incomplete 1 is complete
        completion_status = 0 if user_calc_update[0].stage == 3 else 1
        return JsonResponse({
            'message': f'{stage_message} {results_message}',
            'completion_status': completion_status
        })  

    except UserCalculationUpdate.DoesNotExist:
        return JsonResponse({
            'message': 'Beginning Calculations',
            'completion_status': 0
        })  

add to your urls:

url(r'^calculation_poll/$', view.poll_view, name='poll_view')

make sure to add {% load url %} to the top of your template,
add jquery by including <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> in your header if you haven't already, and add this script someonwhere in your template:

<script>
    // $('#id') is the jquery selector syntax for selecting an element by its id
    // and manipulating it.
    $('#calc_submit_button_id').click(function(){ //adding an onclick trigger
        $('#loading').show(); //the 'loading' element is where you want your                      //information about your progress to show, this can
                              // a modal or a bootstrap alert or whatever you like.
                              // the .show() method removes any 'display:none' or
                              // 'display: hidden' from the style of the 'loading'
                              // element.
        pollingFunction = $.ajax({
            dataType: "json",
            url:"{% url "poll_view" %}",
            data: '',
            success: function (message){
                /* use console.log() and look at the console using inspect element
                (ctrl +  shift + i in chrome, right click + Q in firefox) to 
                examine the structure of your message
                */
                console.log(message)
                # Some code that decides what to do with message
                if (you wish to display the message and poll again)
                {   
                    //adds message to ('#loading'), should make it prettier, but
                    //serves our purpose for now
                    ('#loading').empty()
                    ('#loading').html('<h3>' + JSON.stringify(message) + '</h3>')
                    //wait ten seconds, poll again
                    setTimeout(pollingFunction, 10000)
                }
                else(you wish to end polling and hide the ('#loading') element)
                {
                    $('#loading').hide();
                }
            },
            error: function(jqXHR){
                console.log(jqXHR)
                ('#loading').html('<h3>Oh no, something awful happened, better check the logs.</h3>')
            }
        });
    });
</script>
DragonBobZ
  • 2,194
  • 18
  • 31
1

You will need to store the progress somewhere during the calculation.

Everytime the client polls via a GET request you can send the stored progress data.

In pseudocode:

def form(request):
    if request.method == 'POST'
    form = MyForm(request.POST)
        if form.is_valid():
             form = convert_form_to_model(form.cleaned_data, MyModel)
             # DO calculations
             save('part1 complete')
             send_message_to_user_waiting_screen('Part One Complete')
             # DO more calculations
             save('part2 complete')
             send_message_to_user_waiting_screen('Part two Complete')
             ....
             return HttpResponseRedirect(reverse('my_app:results', args(form.id,)))
        else:
            return render(request, 'my_app/form.html')
    else:
        form = MyForm()
        render(request, 'my_app/form.html')


#Another endpoint to respond to polling
def progress():
    return fetchprogress(uid, some other arg)
Ricky Han
  • 1,309
  • 1
  • 15
  • 25
  • Sorry but I do not really understand this. what is `save()` and what is `progess()` and `fetchprogress` – SumNeuron Jun 14 '17 at 08:09
  • These are pseudocode. `save()` means `just save the progress to a global variable or a database or redis`. and `fetchprogress()` means `somehow get it back.` – Ricky Han Jun 14 '17 at 08:16
  • I get that is pseudocode. `send_message_to_user_waiting_screen` is also pseudo code. My question is how to *actually* write a function that does this... – SumNeuron Jun 14 '17 at 08:34
  • Really depends on your existing code. But do checkout this example: https://stackoverflow.com/questions/26514583/text-event-stream-recognised-as-a-download – Ricky Han Jun 14 '17 at 08:37
0

You shouldn't update status all the time (about %)

When you call Ajax, just pop-up a dialog (or some image, gif...) for the processing

And then, when Ajax done (success or fail) just shut it down.

Like:

$('#btn').click(function(){
        $('#loading').show();
        ajaxAction = $.ajax({
                dataType: "json",
                url:"some/url/",
                data: data_to_send,
                success: function (data){
                    # Some code
                    $('#loading').hide(1500);
                }
            });
    });

And remember to create the html with id = "loading" (as i mention)

Nam Nguyễn
  • 552
  • 4
  • 20