Django - Prevent admin action to run twice - django

My problem is simple, I have an admin action named "send email":
The problem is, the admin user can call this function twice in a row, probably by clicking "Go" many times; this causes the email to be sent multiple times. Is there a way to prevent it?
I tried to put a timeout in the end of the admin function send_email but it's not working: it takes 10 seconds between each users, and I have many users.
def send_email(modeladmin, request, queryset):
...
time.sleep(10)

You can use JavaScript to prevent double clicking on the button.
To add javascript to your admin, you can override the admin HTML templates or adding a javascript using this approach
const button = document.querySelector("#my-go-button");
let didIClickGo = false;
button.addEventListener("click", (event) => {
if (!didIClickGo) {
didIClickGo = true;
} else {
event.preventDefault();
}
});
Note: on line 1, point the selector to your button.
Another option would be rate-limiting on the backend where you would save the data about the emails and whenever you call the function, you should check if you sent an email to this user in the last couple seconds maybe.

You can check if the queryset is different from the last time to avoid calling send_email twice in a row:
old_queryset = None
def send_email(modeladmin, request, queryset):
if self.old_queryset != queryset:
self.old_queryset = queryset
... # Inserts your code here in the "if" block
You must define old_queryset as a property of the ModelAdmin subclass.

Related

Django run a function while exiting a view or navigating to a different URL from the current URL

In my django application, when I visit a particular URL ex:enter_database, a view function is called that adds database entries. Now when I visit a different URL, I want to clear the database entries.
My question, is it possible to call a method while leaving a view/URL.
Note: I can clear the entries by adding the logic in every other view, which is not the approach I want to do. I am looking for a way to call a method while exiting the current displayed view.
In the end of your view you have to create response object and return it.
So I don't know is a correct Django way or not, but you can create custom reponse class and insert logic inside here
class HttpResponseWithDataClearing(HttpResponse):
def __init__(self, content=b'', *args, **kwargs):
# Some custom logic here (clear the entries?)
super().__init__(content, *args, **kwargs)
After that change view's return statement
return HttpResponse(...)
↓
return HttpResponseWithDataClearing(...)
Where you want to add custom logic.
If you want to add logic when already response sent and you moving to another page, that is impossible to do at backend.
You must to set javascript action on page leaving. And do ajax request to data_clear_url
window.onunload = function() {
do_ajax_request("data_clear_url");
}
EDIT1: onunload method not working
I tried to reproduce javascript onunload method and looks like ajax request is not properly working with Chrome in this case. You can check this article

Refresh Django wizard form after browser's back button

I am using SessionWizardView from django-formtools project.
I've noticed that after successfully passing all form checks and executing done() function, which redirects to completely different view, user can still hit browser Back button and re-fill form again.
Isn't there any way to prevent that? I would assume that it would be some kind of session cleaning mechanism. But I cannot find any in documentation.
After some playing around I've found that it can be achieved in two lines:
def done(self, form_list, form_dict, **kwargs):
#regular form processing
self.instance_dict = None
self.storage.reset()
Now, after pressing Back button and submitting form it fails because no data exists and resets to first screen.

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: 2 active users in a specific url

I simply don't know how to do it.
I have an html linked with a knew url, with two buttons.
imagine that url is only shared with two persons/users.
each person have to push the button signed their name.
I tried using request, but I couldn't figure out how to know if more than one user is active in that view.
The another point is:
One person reach the url first, how to make an "refresh" in their browser, when the second one appear?
You'd have to store who's at what view in some model.
class UsersInURL(models.Model):
users = models.ManyToManyField(User)
url = models.CharField()
# you'd probably want to use a through table and set up some expiration logic
Then remember to modify this model every time a logged in user views a page; middleware sounds perfect for this.
class UserTrackerMiddleware(object):
def process_view(self, request, *args, **kwargs):
UsersInURL.users.through.filter(user=request.user).delete()
users_in_url, created = UsersInURL.objects.get_or_create(url=request.path)
users_in_url.users.add(request.user)
Then to refresh their page, you'd need to set some kind of communication between the server and the browser which pings this model and refreshes if it detects some change.
var lastCheckedUsers = undefined;
function checkForNewUsers() {
$.get('/some-ajaxy-url/', { url: document.URL }, success: function(data) {
// you'd have to set up a view that returns active users # url
if (lastCheckedUsers != data.users && lastCheckedUsers != undefined) {
window.location.href = window.location.href;
};
lastCheckedUsers = data.users; // store last state; refresh if modified.
})
};
setInterval(checkForNewUsers, 1000);
I'm sure that should get some ideas flowing.

Celery task model instance data being stomped by web worker?

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.