Celery task model instance data being stomped by web worker? - django

I have a task that gets called on one view. Basically the task is responsible for fetching some pdf data, and saving it into s3 via django storages.
Here is the view that kicks it off:
#login_required
#minimum_stage(STAGE_SIGN_PAGE)
def page_complete(request):
if not request.GET['documentKey']:
logger.error('Document Key was missing', exc_info=True, extra={
'request': request,
})
user = request.user
speaker = user.get_profile()
speaker.readyForStage(STAGE_SIGN)
speaker.save()
retrieveSpeakerDocument.delay(user.id, documentKey=request.GET['documentKey'], documentType=DOCUMENT_PAGE)
return render_to_response('speaker_registration/redirect.html', {
'url': request.build_absolute_uri(reverse('registration_sign_profile'))
}, context_instance=RequestContext(request))
Here is the task:
#task()
def retrieveSpeakerDocument(userID, documentKey, documentType):
print 'starting task'
try:
user = User.objects.get(pk=userID)
except User.DoesNotExist:
logger.error('Error selecting user while grabbing document', exc_info=True)
return
echosign = EchoSign(user=user)
fileData = echosign.getDocumentWithKey(documentKey)
if not fileData:
logger.error('Error retrieving document', exc_info=True)
else:
speaker = user.get_profile()
print speaker
filename = "%s.%s.%s.pdf" % (user.first_name, user.last_name, documentType)
if documentType == DOCUMENT_PAGE:
afile = speaker.page_file
elif documentType == DOCUMENT_PROFILE:
afile = speaker.profile_file
content = ContentFile(fileData)
afile.save(filename, content)
print "saving user in task"
speaker.save()
In the meantime, my next view hits (actually its an ajax call, but that doesn't matter). Basically its fetching the code for the next embedded document. Once it gets it, it updates the speaker object and saves it:
#login_required
#minimum_stage(STAGE_SIGN)
def get_profile_document(request):
user = request.user
e = EchoSign(request=request, user=user)
e.createProfile()
speaker = user.get_profile()
speaker.profile_js = e.javascript
speaker.profile_echosign_key = e.documentKey
speaker.save()
return HttpResponse(True)
My task works properly, and updates the speaker.page_file property correctly. (I can temporarily see this in the admin, and also watch it occur in the postgres logs.)
However it soon gets stamped over, I BELIEVE by the call in the get_profile_document view after it updates and saves the profile_js property. In fact I know this is where it happens based on the SQL statements. Its there before the profile_js is updated, then its gone.
Now I don't really understand why. The speaker is fetched RIGHT before each update and save, and there's no real caching going on here yet, unless get_profile() does something weird. What is going on and how might I avoid this? (Also, do I need to call save on speaker after running save on the fileField? It seems like there are duplicate calls in the postgres logs because of this.
Update
Pretty sure this is due to Django's default view transaction handling. The view begins a transaction, takes a long time to finish, and then commits, overwriting the object I've already updated in a celery task.
I'm not exactly sure how to solve for it. If I switch the method to manual transactions and then commit right after I fetch the echosign js (takes 5-10 seconds), does it start a new transaction? Didn't seem to work.
Maybe not
I don't have TransactionMiddleware added in. So unless its happening anyway, that's not the problem.

Solved.
So here's the issue.
Django apparently keeps a cache of objects that it doesn't think have changed anywhere. (Correct me if I'm wrong.) Since celery was updating my object in the db outside of django, it had no idea this object had changed and fed me the cached version back when I said user.get_profile().
The solution to force it to grab from the database is simply to regrab it with its own id. Its a bit silly, but it works.
speaker = user.get_profile()
speaker = Speaker.objects.get(pk=speaker.id)
Apparently the django authors don't want to add any kind of refresh() method onto objects, so this is the next best thing.
Using transactions also MIGHT solve my problem, but another day.
Update
After further digging, its because the user model has a _profile_cache property on it, so that it doesn't refetch every time you grab the profile in one request from the same object. Since I was using get_profile() in the echosign function on the same object, it was being cached.

Related

Django - Show history in admin working but only when actions take place in admin

In Django admin, when you are looking at a record there is a button called "history". When you click on this button, it tells you when the record was created and by whom and also each time a field was changed and by whom. I find this extremely useful. However, I noted that these only show up for actions that are done on the admin page. So when I change the record through a view, this is not displayed in that history. Is there an easy way to have my views store this information also so that it will all be visible from the admin page?
Thanks so much in advance for your help.
The admin app comes with a model - LogEntry. Every time you do something in the admin app, there is some code somewhere that saves a LogEntry. This is how the app works. For example in the changeform_view there is something like this:
def changeform_view(self, request, ...):
...
if request.method == "POST":
if all_valid(formsets) and form_validated:
# do some other stuff then
self.log_change(request, new_object, change_message)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
LogEntry.objects.log_action is where the log is created. Unfortunately, this doesn't happen anywhere else, unless you were to make it happen.
There's nothing stopping you from doing this though. You can create a LogEntry wherever you want.
Having said that, it might be a bit confusing since when you see a LogEntry you now no longer no if that is a change that has happened because of someone manually changing some data via the admin app, or a change that has occurred programatically. It would probably be a better idea to create your own Log model, and save logs where and when you want.
You can always display your Logs in your in the relevant admin view should you so wish. Something like this will do the trick:
class MyAdmin(admin.modelAdmin):
readonly_fields = (logs_field,)
def logs_field(self, instance):
logs = Log.objects.filter(object=instance)
return format_html_join(
'\n', "<p>{}: {}</p>",
((log.date, log.message) for log in logs)
)

Django sending context to multiple views

I'm trying to achieve following tasks in Django:
First page: User fills a large Job application form and that data is
sent to Second page
Second page: User reviews his previously filled data and proceeds to third page
Third page: User pays the amount(came from Second page) and once paid, then only all this data is saved to DB.
I've done First page work of filling form and then sending that data to Second page.
Here's my views.py
def applyJob(request,id=None):
job = get_object_or_404(AddJob, id=id)
if request.method == 'POST':
context = {
'jobName': job.jobName,
'jobID' : job.pk,
'cfName' : request.POST.get('candidateFirstName'),
'cmName' : request.POST.get('candidateMiddleName'),
'clName' : request.POST.get('candidateLastName'),
......
return render(request,'reviewAppliedJob.html',context)
else:
context ={
"jobName": job.jobName,
"id": id,
}
return render(request,'applyJob.html',context)
Since I'm sending the context data using return render(request,'reviewAppliedJob.html',context), URL is not changing and so it's not going to my reviewAppliedJob views and so I cannot write code to go to Third page.
def reviewAppliedJob(request):
return HttpResponse('done....')
For that, I can use HttpResponseRedirect('/reviewAppliedJob') instead of render in applyJob() but it will not send my context data. Django allows sending context either using render(), messages framework, url params or session. But since I have large amount of data, I don't know which one to use and will work perfectly.
What's the best way to achieve this task efficiently and securely?
Thanks in advance.
I suggest you use sessions instead. Instead of writing three individual views you can write one. After user fills the data, save them in a session key with a phase key temporarily. In this way, when the view is processed you can access to data and phase of the user.

Django - How to stay on the same page without refreshing page?

I am using Boostrap modal fade window which renders Django form to update my database records. And what I fail to do is not to reload the page if the user has opened the Update window and did not change anything. It will be easier to get my idea if you look at the code below:
def updateTask(request, task_id):
#cur_usr_sale_point = PersonUnique.objects.filter(employees__employeeuser__auth_user = request.user.id).values_list('agreementemployees__agreement_unique__sale_point_id',flat=True)
selected_task = Tasks.objects.get(id=task_id)
task_form = TaskForm(instance=selected_task )
taskTable = Tasks.objects.all()
if request.method == 'POST':
task_form = TaskForm(request.POST,instance=selected_task)
if task_form.has_changed():
if task_form.is_valid():
# inside your model instance add each field with the wanted value for it
task_form.save();
return HttpResponseRedirect('/task_list/')
else: # The user did not change any data but I still tell Django to
#reload my page, thus wasting my time.
return HttpResponseRedirect('/task_list/')
return render_to_response('task_management/task_list.html',{'createTask_form':task_form, 'task_id': task_id, 'taskTable': taskTable},context_instance=RequestContext(request))
The question is, is there any way to tell Django to change the url (like it happens after redirecting) but not to load the same page with same data for the second time?
It's not trivial, but the basic steps you need are:
Write some javascript to usurp the form submit button click
Call your ajax function which sends data to "checking" view
Write a "checking" view that will check if form data has changed
If data have changed, submit the form
If not, just stay on page
This blog post is a nice walkthrough of the entire process (though targeted towards a different end result, you'll need to modify the view).
And here are some SO answers that will help with the steps above:
Basically:
$('#your-form-id').on('submit', function(event){
event.preventDefault();
your_ajax_function();
});
Call ajax function on form submit
Gotta do yourself!
Submit form after checking

Django Overwrite form data saved

I've posted about this problem before, but I still haven't found a solution so I'm hoping I'll have better luck this time.
I have a form that takes inputted data by the user. In another page, I am creating the identical form that the user has populated (pre-filled with that information) for editing purposes. Users will come to this page to EDIT the information they have already put in. My problem is that it isn't overwriting the instance.
def edit(request):
a = request.session.get('a', None)
if a is None:
raise Http404('a was not found')
if request.method == 'POST':
form = Name_Form(request.POST, instance=a)
if form.is_valid():
j = form.save( commit=False )
j.save()
else:
form = Name_Form( instance = a )
For this form, I'm using "unique_together" for some of the values. I'm also calling on `{{ form.non_field_errors }} in the template.
What is happening is when I make changes in the editing view, if the fields changes involves those defined in "unique_together" then an error is returned telling me that the instance already exists. Otherwise it saves a new instance. It isn't OVERWRITING.
Note that the reason i am using unique_together is that I want to prevent users from initially inputting the same form twice (before the editing stage, in the initial inputting view).
Any ideas?
EDIT: note that "a" refers to a session that includes a drop down box of all the available instances. This carried forward will indicate which instance the user wants to edit.
`
Why not do a database lookup of the model your trying to save and pull the fields from the form to the model then save the model?
Instead to store model a in session you should store it on database. Then edit it:
def edit(request, pk):
a = A.objects.get( pk = pk)
...
pk it the a identifier, you can send it to view via urls.py. I encourage to you to use POST/Redirect/GET pattern.
You can add a 'state' field on your model to control workflow (draft, valid)
You should not save objects in the session. If you really need to use a session - save a PK there and retrieve object right before giving it to Form. But the better solution is to send it in GET or POST parameters or included in url. Sessions are unreliable, data inside it can be destroyed between user's requests.
And you can retrieve value from a session in a more pythonic way:
try:
a = request.session['a']
except KeyError:
raise Http404('a was not found')

Using django-socialregistration and getting 'Facebook' object has no attribute 'graph'

I've installed djangosocialregistration and it seemed like it was working fine for a while, but now I'm getting an error and I can't figure out where it's coming from. Inside my view I'm doing this to start looking at the API...
me = request.facebook.graph.get_object("me")
and I'm getting this...
'Facebook' object has no attribute 'graph'
After it quit working I rolled back a couple small changes I'd made, reset everything, deleted cookies and it's still not working. I'm running django 1.1.1 and it's slightly difficult for me to upgrade, not impossible though. I've been reloading a bunch trying to get it working, is there any possibility facebook throttles login connections on their end?
The Facebook class in the middleware of socialregistration looks like this:
class Facebook(object):
def __init__(self, user=None):
if user is None:
self.uid = None
else:
self.uid = user['uid']
self.user = user
self.graph = facebook.GraphAPI(user['access_token'])
If no user is set on __inii__ it will simply not set graph. In the Middleware this should be set via:
fb_user = facebook.get_user_from_cookie(request.COOKIES, getattr(settings, 'FACEBOOK_APP_ID', settings.FACEBOOK_API_KEY), settings.FACEBOOK_SECRET_KEY)
request.facebook = Facebook(fb_user)
So my guess that the cookie from Facebook is not set for your site. Maybe you add some debug logging to determine if there is a cookie from facebook or not.
Another guess would be that request.facebook is overwritten somewhere. Maybe you check this as well.