Concurrency protection is not working - django

I go bananas here. With my attempts to implement concurrency protection.
So I figured out there are 2 ways to do it.
Optimistic - basically we add a version field to the record and each save will increase it. So if my current version field is different that one on the disc it means something got modified and I give an error.
And the pessimistic approach that means we just lock a record and it will be not possible to edit.
I implemented both ways plus concurrency package for Django and nothing works I teas on SQLLite and on Postgres with Heroku.
Below is my view method it has both approaches
for pessimistic, I lock with transaction atomic using select_for_update
and for optimistic I use concurrency django package that increases my version field on each edit and do a simple comparison with on_disc value
What I do wrong? Please help?
#login_required
def lease_edit(request,pk,uri):
logger = logging.getLogger(__name__)
title = 'Edit Lease'
uri = _get_redirect_url(request, uri)
with transaction.atomic():
post = get_object_or_404(Lease.objects.select_for_update(), pk=pk)
if request.method == "POST":
form = LeaseForm(request.POST, instance=post)
if form.is_valid():
lease = form.save(commit=False)
on_disc= Lease.objects.get(pk=post.id)
if on_disc.version > post.version:
messages.add_message(request, messages.ERROR, str(lease.id) + "-Error object was modified by another user. Please reopen object.")
return redirect(uri)
lease.save()
messages.add_message(request, messages.SUCCESS, str(lease.id) + "--SUCCESS Object saved successfully")
return redirect(uri)
else:
form = LeaseForm(instance=post)
#lease = post.lease
return render(request, 'object_edit.html', {'form': form, 'title':title, 'extend': EXTEND})
To test I just open 2 browsers with the same record and try to edit both in parallel (I use same user)

The problem is with how websites work. Currently your locking works like this:
GET request loads data
you lock the row, load, send to browser, unlock
you edit data on the browser
POST data to server
you lock the row, update, unlock
There is nothing here that keeps the row locked. Multiple instances can send data to the server since the row is unlocked after each request. This is the way web systems usually work, each request is separate and locking has to be done differently.
As for the row versioning, if I understand the code correctly you don't send the current row number to the browser but you read it while updating. This again means that you get the correct number. You should send the current number to the browser and on POST compare that number to the current number in the database. This way if something changes it detects it.
I may be wrong about this part since I'm not familiar with Django but this is my understanding of the functionality of the code. I would assume form might have the current row version but it's not compared to anything, only the current number in database and one after update (which also might change the number possibly).

Related

Flask session cookie reverting

I am sure I am probably being stupid but struggling to wrap my head around this one.
I have a flask website and I am setting up a checkout page for it so users can add their items to the cart etc. Everything was going great, I was able to add items to the cart, get a total etc (using sessions) however when I have tried to implement the ability for users to update the cart on the checkout page, when my form posts, the session data only survives the initial load. The print statement shows the data I am collecting is fine, and the session cookie is set initially, as everything updates, however the moment I then change page, it reverts to whatever it was before I made the update.
#views.route("/shopping-cart", methods=['GET','POST'])
def to_cart():
clear_cart = 'views.to_clear_cart'
if 'shopping' in session:
shopping_list = session['shopping']
sum_list = []
for quantity, price in shopping_list.values():
sum_list.append(quantity * price)
total = sum(sum_list)
if request.method == "POST":
new_quantity = int(request.form.get('quantity'))
product_id = request.form.get('issue')
unit_price = int(request.form.get('price'))
print(new_quantity, product_id, unit_price)
shopping_list[f'{product_id}'] = [new_quantity, unit_price]
return redirect(url_for('views.to_cart'))
return render_template("cart.html",
shopping_list=shopping_list,
total=total,
clear_cart=clear_cart,
)
else:
return render_template("cart.html",
clear_cart=clear_cart
)
I just do not really understand why it is not updating as from what I can tell, the code is running fine, and it does update, but then the session cookie just reverts itself to whatever it was before (using browser side cookies for this for testing).
Any help appreciated!!
After much confusion as everything seemed to be working absolutely fine after I rewrote this in about 5 different ways and printed half the app in the console, I finally found the answer and it is indeed me being an idiot.
It turns out if you modify a value in place rather than creating or deleting it does not automatically save the session state and you just need to state explicitly that it has been modified.
Turns out the answer was as simple as this line of code.
session.modified = True

Django function execution

In views, I have a function defined which is executed when the user submits the form online. After the form submission there are some database transactions that I perform and then based on the existing data in the database API's are triggered:
triggerapi():
execute API to send Email to the user and the administrator about
the submitted form
def databasetransactions():
check the data in the submitted form with the data in DB
if the last data submitted by the user is before 10 mins or more:
triggerapi()
def formsubmitted(request):
save the user input in variables
Databasetransactions()
save the data from the submitted form in the DB
In the above case, the user clicks on submit button 2 times in less than 5 milliseond duration. So 2 parallel data starts to process and both trigger Email which is not the desired behavior.
Is there a way to avoid this ? So that for a user session, the application should only accept the data once all the older data processing is completed ?
Since we are talking in pseudo-code, one way could be to use a singleton pattern for triggerapi() and return Not Allowed in case it is already istantiated.
There are multiple ways to solve this issue.
One of them would be to create a new session variable
request.session['activetransaction'] = True
This would however require you to pass request, unless it is already passed and we got a changed code portion. You can also add an instance/ class flag for it in the same way and check with it.
Another way, which might work if you need those submissions handled after the previous one, you can always add a while request.session['activetransaction']: and do the handling afterwards.
def formsubmitted(request):
if 'activetransaction' not in request.session or not request.session['activetransaction']:
request.session['activetransaction'] = True
# save the user input in variables
Databasetransactions()
# save the data from the submitted form in the DB
request.session['activetransaction'] = False
...

Multi-DB Transactions

Django Version 1.10.5 with Postgres 9.6.1
For the last year I've been working in a multi-schema default database environment. However things are beginning to grow to the point I've decided to split the single database into 3 databases.
I've got things working with a master/slave router for all 3 databases.
I am not using the 'default' database key. Instead I have 'db1', 'db2', and 'db3'
The part I am confused about is with transactions in this multi-database environment.
In this example it fails as expected. Caused of course by not using #transaction.atomic(using='db1') which is clear to me.
#transaction.atomic()
def edit(self, context):
"""Edit
:param dict context: Context
:return: None
"""
# Check if employee exists
try:
result = Passport.objects.get(pk=self.user.employee_id)
except Passport.DoesNotExist:
return False
result.name = context.get('name')
result.save()
However I have this strange example, simply because I'm trying to understand... I would have expected this to fail but it does not:
#transaction.atomic(using='db1')
def edit(self, context):
"""Edit
:param dict context: Context
:return: None
"""
# Check if employee exists
try:
result = Passport.objects.get(pk=self.user.employee_id)
except Passport.DoesNotExist:
return False
result.name = context.get('name')
with transaction.atomic(using='db2'):
result.save()
The model Passport does not exist in DB2 models at all.
My router is setup so that all writes go to each respected DB.
So what is the purpose of setting the using='db1' in the atomic transaction? I've looked at the source and I see it defaults to default when not "using".
In the above example I even made another transaction inside of the initial transaction but this time using='db2' where the model doesn't even exist. I figured that would have failed, but it didn't and the data was written to the proper database.
I bring this up because there will be situations where I need to interact with all 3 databases and if a single problem occurs when writing to all 3 databases, all 3 need to be rolled back or if on success of everything, then committed of course.
Perhaps someone can help break this down for me so I can understand?
You're interpreting transaction.atomic(using='X') to mean: run the following database commands on X, inside a transaction.
In fact, it just means: open a transaction on database X, and then either commit it or roll it back at the end of the block.
Or, as the documentation puts it:
Under the hood, Django’s transaction management code:
opens a transaction when entering the outermost atomic block;
commits or rolls back the transaction when exiting the outermost block.
The question of which database to use for a given command is determined by your router, not the using clause. So your transaction.atomic(using='db2') block is pointless (it will simply open a transaction on db2 and then close it), but not an error.

How to Avoid multi post request by one click?

I have a simple form which adds personal information of a family. sometime it saves two instance of a person just by one submit. Maybe my mouse has problem and and double clicks instead of one click (it has some problems). I think this is not possible and django accepts just one post request from an instance of a form and not more (maybe it accepts). what if may code has problem? if it is problem of my code, why it happens once a while?
house = get_object_or_404(House, id=code)
if request.method == 'POST':
form = ParentForm(request.POST)
if form.is_valid():
# save it if it's valid
parent = form.save(commit=False)
if parent.living == 0:
parent.in_family = 0
if not parent.guardian:
parent.save()
if parent.guardian and parent.in_family:
parent.save()
I use Django 1.8
Edit to clear: this is not the only view sometime saves twice. Maybe it is a bug in django
To solve this problem, first you need to create unique constraint on the corresponding database table. The real solution is based on the database schema. I don't know what fields (columns) you have in the parent table, you can start from add unique constraint to these two fields: child_id and parent_name.
The other problem is you need to prevent the second click. So basically you need to write some JavaScript code: it listens to the onClick event of the submit button. Once the button get clicked, the listener sets the disabled attribute to that button to prevent further clicks.

store a list in request.session in one view and retrieve it in another view django

I have a huge list that will be generated dynamically from a csv file in a django view, but i had a requirement to use that list in the next view, so i thought to give a try on django sessions
def import_products(request):
if request.method == 'POST':
if request.FILES:
csv_file_data = ...........
total_records = [row for row in csv_file_data]
request.session['list_data'] = total_records
# total_records is `list of lists` of length more than 150
# do some processing with the list and render the page
return render_to_response('product/import_products.html')
def do_something_with_csv_data_from_above_view(request):
data = request.session['list_data']
# do some operations with list and render the page
return render_to_response('website/product/success.html',
So as mentioned in the above, i need to use the total_records huge list in the do_something_with_csv_data_from_above_view view and delete the session by storing it in another variable
So actually how to implement/use exactly the sessions concept(i have read the django docs but could n't able to get the exact concept)
In my case,
When a user tries to upload the csv file each time, i am reading the data and storing
the data as list to session
==> Is this the right way to do so ? also i want to store the session variable in
database concept
Because the list was huge, i need to delete it for sure in the next view when i
copied it in to another variable
Am i missing anything, can anyone please implement exact code for my above scenario ?
You have two options:
Client side RESTful - This is a RESTful solution but may require a bit more work. You can retrieve the data in the first request to the client and after selecting you can send the selected rows back to the server for processing, CSV etc.
Caching: In the first request you can cache your data on the server using a django file system or memcached. In the second request use the cache key (which would be the user session key + some timestamp + whatever else) to fetch the data and store in the db.
If it's a lot of data option 2 may be better.