Is it possible to submit a django form to run 2-3 large tasks at the backend and display the user a page for status/progress of the tasks running and display the output on completion.
I have a function as below in my view.py file which executes large tasks. I want that while the tasks are been running in the back-end on my server(ubuntu), instead of showing the user with the page/browser loading (which hangs for huge tasks), I want to show a page to show the tasks status/progress.
views.py
def test(request) :
if request.method == 'POST': # If the form has been submitted...
form = TestForm(request.POST)
if form.is_valid(): # All validation rules pass
form_data = form.cleaned_data
## Get the form data and use it
output, error = executeCommand("##Perform the required action with the form data")
if error == '' :
return HttpResponse('Work Done !! Thanks')
else :
return HttpResponse('Error Occurred')
else:
form = TesForm() # An unbound form
return render_to_response("test.html", { 'form' : form } , context_instance=RequestContext(request))
Any help/advice is greatly appreciated.
I think your webserver will get stucked if your "executeCommand" take very long time to finish.
You might use message queues as suggested by toabi.
You might have a look on django-celery.
Related
Currently, I have a view that essentially closes a lead, meaning that it simply copies the information from one table (leads) to another (deals), now what I really would like to do is that after clicking close, the user is redirected to another page where the user can update some entries (sales forecast), I have a view that updates the lead, so I thought that I can do something like below:
#login_required
def close_lead(request):
id = request.GET.get('project_id', '')
keys = Leads.objects.select_related().get(project_id=id)
form_dict = {'project_id': keys.project_id,
'agent': keys.agent,
'client': keys.point_of_contact,
'company': keys.company,
'service': keys.services,
'licenses': keys.expected_licenses,
'country_d': keys.country
}
deal_form = NewDealForm(request.POST or None,initial=form_dict)
if request.method == 'POST':
if deal_form.is_valid():
deal_form.save()
obj = Leads.objects.get(project_id=id)
obj.status = "Closed"
obj.save(update_fields=['status'])
## Changing the Forecast Table Entry
forecast = LeadEntry.objects.filter(lead_id=id)
for i in forecast:
m = i
m.stage = "Deal"
m.save(update_fields=['stage'])
messages.success(request, 'You have successfully updated the status from open to Close')
update_forecast(request,id)
else:
messages.error(request, 'Error updating your Form')
return render(request,
"account/close_lead.html",
{'form': deal_form})
This view provides the formset that I want to update after closing the lead
#login_required
def update_forecast(request,lead_id):
# Gets the lead queryset
lead = get_object_or_404(Leads,pk=lead_id)
#Create an inline formset using Leads the parent model and LeadEntry the child model
FormSet = inlineformset_factory(Leads,LeadEntry,form=LeadUpdateForm,extra=0)
if request.method == "POST":
formset = FormSet(request.POST,instance=lead)
if formset.is_valid():
formset.save()
return redirect('forecast_lead_update',lead_id=lead.project_id)
else:
formset = FormSet(instance=lead)
context = {
'formset':formset
}
return render(request,"account/leadentry_update.html",context)
As you can see I’m calling this function update_forecast(request,id) after validating the data in the form, and I would have expected to be somehow redirected to the HTML page specified on that function, however, after clicking submit, the form from the first view is validated but then nothing happens, so I'm the function doesn't render the HTML page
My question how can I leverage existing functions in my views?, obviously, I will imagine that following the DRY principles you can do that in Django, so what am I doing wrong ?, how can I call an existing function within another function in views?
A view returns a response object. In your current code, you're calling a second view but not doing anything with its response. If you just wanted to display static content (not a form that might lead to an action that cares about the current URL) you could return the response object from the second view - return update_forecast(request, id).
But since your second view is displaying a form, you care about what the action for the view from the second form is. The typical Django idiom is to have forms submit to the current page's URL - that wouldn't work if you just call it and return its response object. You could customize the action in the second view, say adding an optional parameter to the view, but the usual idiom for form processing is to redirect to the view you want to show on success. Just as you do in the update_forecast view. Something like this:
messages.success(request, 'You have successfully updated the status from open to Close')
return redirect('update_forecast', lead_id=id)
I'm confused with how django adds elements to a list. consider the following:
def add(request):
if request.method == "POST":
form = NewTaskForm(request.POST)
if form.is_valid():
task = form.cleaned_data["task"]
request.session['tasks'].append(task)
# request.session['tasks'] += [task]
return HttpResponseRedirect(reverse("tasks:index"))
else:
return render(request, "tasks/add.html",{
"form": form
})
return render(request, "tasks/add.html",{
"form": NewTaskForm()
})
if we add a print statement after request.session['tasks'].append(task) we get a list:
['check email']
we also get the same list if we comment the append line and use the correct way with +=
However, on the redirect to task/index the first way shows an empty list and the second way shows the list that's expected. Why? Whats going on?
Django only saves the session data and sends to client if it has been assigned or deleted. Like in your second example:
request.session['tasks'] += [task]
If you are updating the information inside your session data, it will not recognize the change and won't update it, like when you append some data to the list that is assigned to the 'tasks' key. In this case you need to explicitly tell Django that you modified the session data using:
request.session.modified = True
I have a Django app where users would upload Images and certain functions would take the image file path to process them and save the result in the same model.
all of this would happen in the file upload view the problem is My functions take the file path which isn't created/committed in the DB yet as I don't save before calling those functions.
I tried overriding the save method in the models.py and it didn't work so how can I call the functions after the upload in a convenient way ??
here's the function:
# The view for analysing images
def upload_view(request,pk=None):
patient = get_object_or_404(Patient,pk=pk)
if request.method == 'POST':
form = forms.ImageForm(request.POST,request.FILES)
if form.is_valid():
image = form.save(commit=False)
image.patient = patient
image.enhanced = main(image.original.path)
image.segmented = segment(image.enhanced.path)
image.enhanced.name = image.enhanced.path.split('media')[1]
image.segmented.name = image.enhanced.path.split('media')[1]
messages.success(request,"Image added successfully!")
image.save()
return HttpResponseRedirect(reverse_lazy('patients:patient_detail', kwargs={'pk' : image.patient.pk}))
else:
form = forms.ImageForm()
return render(request, 'patients/upload.html', {'form': form})
else:
form = forms.ImageForm()
return render(request, 'patients/upload.html', {'form': form})
image.original is the uploaded image
the problem is the file path isn't passed correctly and the functions return errors bec of that. (it worked when I made the processing in a different view where it was accessed after the upload)
Before you call save() on your model, the path to your image doesn't exist, or is temporary. You can fix this by first creating the model from the form, no need for commit=False, assuming main and segment are PIL based functions that return images, you can use the ImageField.save() method to set the name at the same time:
if form.is_valid():
image = form.save() # this creates the model, the upload paths are created here!
image.patient = patient
image.enhanced.save("enhanced_" + image.original.name, main(image.original.path)) # I made this up to avoid conflicts in your storage
image.segmented.save("segmented_" + image.original.name, segment(image.enhanced.path))
image.save() # this updates the model with the new images
messages.success(request,"Image added successfully!") # I moved this here, more accurate, in case save fails....
return HttpResponseRedirect(reverse_lazy('patients:patient_detail', kwargs={'pk' : image.patient.pk}))
As you can see, you need two hits to the database to save your images. This assumes that enhanced and segmented fields are not required in the model.
Also, because image transformation is an expensive task, I'd check how to get this out of the request cycle, by using something like Celery or Django-RQ, that way, your app can still service request while making the transformations on the background.
I am getting date/time info from ajax to Django. I am using 2 different views. event_edit is working fine, but event_edit_new does not work. It return an error Enter a valid date/time.
My question is what is making difference. They are getting exactly same information but one is ok while other one is not.
Javascript code making ajax request:
var ajax_test = function(event){
$.ajax({
url: '/scheduler/event/' + event.eventId + '/edit2/',
method: 'POST', // 'POST' or 'PUT'
data: {
'Note': event.title,
'OpNum': event.resourceId,
'StartTime' : event.start.format(),
'StopTime' : event.end.format(),
'ScheduleNum': event.eventId,
}
}).done(function(res) {
console.log("done", res)
}).fail(function(error) {
console.error("error", error)
console.log(event.start.format());
});
}
2 views
def event_edit(request, pk):
schedule = get_object_or_404(Schedule, pk=pk)
schedule.OpNum = request.POST.get('OpNum')
schedule.Note = request.POST.get('Note')
schedule.StartTime = request.POST.get('StartTime')
schedule.StopTime = request.POST.get('StopTime')
schedule.ScheduleNum =request.POST.get('ScheduleNum')
schedule.save()
return HttpResponse('ok')
def event_edit_new(request, pk):
schedule = get_object_or_404(Schedule, pk=pk)
if request.method =='POST':
form = ScheduleForm(request.POST, request.FILES, instance = schedule)
if form.is_valid():
form.save()
return HttpResponse('ok')
else:
return HttpResponse('error')
else:
return HttpResponse('done')
In your first view, there is no validation applied to user data input:
schedule.StartTime = request.POST.get('StartTime')
schedule.StopTime = request.POST.get('StopTime')
request.POST does not validate any data so that will grab user data exactly as submitted and set it on a model. Note that it might work but it is not guaranteed to work if user will ever send datetime format the application does not understand. For example try submitting something like "invalid date" and you should get 500 error.
Your second view on the other hand uses a Django Form to validate user input. By using a form you are validating user input before processing it in any way. You did not paste how your form is structured however if you are getting Enter a valid date/time. this means Django form did not validate one of the datetime fields. There are couple of reasons why that might be:
submitted datetime does is not one of input_formats for a DateTimeField. You can customize the formats with:
class MyForm(forms.Form):
datetime_field = forms.DateTimeField(input_formats=['%Y-%m-%dT%H:%M:%SZ', ...])
submitted datetime is not one of Django's default supported datetime formats.
You can customize them with DATETIME_INPUT_FORMATS setting.
I have a view like this:
def form1(request):
if request.method == 'POST':
form = SyncJobForm(request.POST)
if form.is_valid():
# do something
in_progress = True
return render_to_response('form1.html', {'in_progress': in_progress})
I would like to know how to set it to refresh the template after is done with the view process. Like rendering the page after its done:
in_progress = True
return render_to_response('form1.html', {'in_progress': in_progress})
# after its finished
finished = True
return render_to_response('form1.html', {'finished': finished})
How can I implement something like this? Thanks in advance.
You can't maintain state between page calls on a global basis, so you'll need to store your data in the database. In addition, a view can't negotiate anything with the browser after it has returned a page, so you need to split this into multiple views and spawn a separate thread for the job. Here's a general outline that might help:
def do_something():
my_job = Jobs.get(id=blah)
my_job.in_progress = True
my_job.save()
# Do some stuff here....
my_job.in_progress = False
my_job.save()
def step1(request):
in_progress = Jobs.get(id=blah).in_progress
if not in_progress:
if request.method == 'POST':
form = SyncJobForm(request.POST)
if form.is_valid():
thread.start_new_thread(do_something)
return HttpResponseRedirect(step2)
else:
return render_to_response('form.html', 'form': form)
else:
form = SyncJobForm()
return render_to_response('form.html', 'form': form)
else:
return HttpResponseRedirect(step2)
def step2(request):
in_progress = Jobs.get(id=blah).in_progress
if in_progress:
return render_to_response('in_progress.html')
else:
return HttpResponseRedirect(finished)
def finished(request):
return render_to_response('finished.html')
Then have your page in_progress.html periodically refresh the page. When the job is completed, you can display a status message in finished.html.
There are more sophisticated ways to do this (write Javascript to poll the server periodically), but you're still going to need to write separate views to respond with the appropriate information. In addition, you could use a job management framework like Celery to create and execute jobs, but again you'll still have to create separate views to handle status information.
I am not able to think of doing this without some sort of call back from the client (unless you use some asynchronous server mechanism, something I am not familiar with). One way would be to have the client poll the server periodically after receiving the "in_progress" notification to see if the processing has finished. The server side can be split into two calls, first to handle the POST as shown in your question and another one to find out and report if a given task is finished.