web2py update_or_insert to increment a counter - sql-update

I have a web2py-defined database table which is used to count the number of times a request has been made by a particular user - so it has integer columns 'user_id' and 'n_req'. I don't want to populate this with a zero for each possible user. Instead, I want to check if a row with the user_id exists and if so, increment the 'n_req' by one, otherwise create it with an initial value of 'n_req' of 1. To avoid race conditions, I'd like to do this using a single update_or_insert call, e.g.
db.count_table.update(db.count_table.user_id == uid, user_id = uid, n_req = n_req + 1)
I presume I can't do this, however, as it is using the pre-existing value of n_req when incrementing. So how do I tell the DAL the initial value for n_req. Can I do, for example, n_req = (n_req || 0) + 1?

I don't think you'll be able to use the .update_or_insert method in this case because the value of n_rec is conditional on whether a record is found. Instead, you can do something like this:
db.define_table('count_table',
Field('user_id', 'integer', unique=True),
Field('n_rec', 'integer', default=1))
def update_count(user_id):
return db(db.count_table.user_id == user_id).update(n_rec=db.count_table.n_rec + 1)
if not update_count(uid):
try:
db.count_table.insert(user_id=uid)
except db._adapter.driver.IntegrityError:
update_count(uid)
Note that the value of n_rec in .update is set to db.count_table.n_rec + 1, which will translate to a SQL statement that will let the database increment the existing value rather than explicitly providing the final value yourself. This should avoid race conditions in case two requests are updating the count at the same time.
Also, note there is a unique constraint on the user_id field. This will prevent a race condition from allowing two records to be created for the same user. In that case, the try/except will catch the resulting IntegrityError, and an update will be made on the existing record.

Related

Flask-SQLAlchemy : Apply filter only if value to compare is not empty

I'm working on a small web-app that includes a filter with around 10/15 different fields,
since the user doesn't have to fill all the fields in order to be able to post the form, I implemented the below logic in order to avoid filtering the database with empty strings or values and obtain 0 results.
if not form.Price_from.data == "" and not form.Price_from.data == None:
Posts_filtered = Posts_filtered.filter(Post.Price >= form.Price_from.datas)
By doing this, I filter the column Post. Price only if the field form.Price_from field contains a value.
Since I have 10-15 fields, you can imagine that I have a lot of lines doing the same thing and I really do not like it. Since I'd like to add more fields in the future, my question is if there is a more efficient way to perform this action?
Do you think it would be faster to implement it with js? By doing that I should not need to send a post request every time and I'd save time.
Thank you!
you can use a helper function and loop through every field:
def is_filled(raw_data):
try:
value = raw_data[0]
if value == '':
return False
except (IndexError, TypeError):
return False
return True
(use raw_data from the field instead of data for this function)
you can more info here: https://stackoverflow.com/a/47450458/11699898

Django: Making queries the efficient way

Would you consider this the right/efficient way to make queries?
discount_code_get = request.GET.get('discount')
discount_code = Discount.objects.filter(code=discount_code_get)
discount_code_exists = discount_code.exists()
if discount_code_exists:
print(discount_code.first().value)
From the docs:
Additionally, if a some_queryset has not yet been evaluated, but you
know that it will be at some point, then using some_queryset.exists()
will do more overall work
You can simple use first() to get required object. Since first returns None if object does not exist you can do something like this:
discount_code = Discount.objects.filter(code=discount_code_get).first()
if discount_code:
print(discount_code)
You here perform two queries: an EXISTS and a fetch. You can merge it into one:
discount_code_get = request.GET.get('discount')
discount_code= Discount.objects.filter(code=discount_code_get).first()
if discount_code is not None:
return discount_code.value
This works since .first() returns None if it can not find such database row.
Or even more efficient (given value is non-NULLable):
# In case value is non-NULLable
discount_code_get = request.GET.get('discount')
discount_value = Discount.objects.values_list(
'value', flat=True
).filter(code=discount_code_get).first()
if discount_value is not None:
return discount_value
In case the code is a unique field, it is more idiomatic to use a try-except here:
# in case code is unique
discount_code_get = request.GET.get('discount')
try:
discount_value = Discount.objects.values_list(
'value', flat=True
).get(code=discount_code_get)
except Discount.DoesNotExist:
pass
else:
print(discount_value)
Using .values_list(..) will reduce the number of columns that are fetched (and deserialized). Although that is typically not a huge boost, it can be significant if the number of columns is large, or the data stored in it is large, since then we save on deserializing attributes, that are never used later in the process.

Using Django reduce(or_) - How can I store an unmatched query with 0 results in a variable

I have a function that takes a user's input from a list of data and searches my database for any items that matches the user's input, and returns all results that are in my database:
results = results.filter(
reduce(or_, (Q(name__icontains=itm.strip()) for itm in query))
)
I would like to handle cases where the user's input is not present in my database. since results fitlers down to what exists, how can I check if the above code failed to find at least one matching result for a query, and store that query in a variable? For example, if results queried my database for the following list: ['one', 'two', 'thee'], assuming 'thee' is not in my database but the other two are, I would like to store the string "thee" in a variable to use later
You can simply evaluate results as a Boolean:
if not results:
print('No match found.')
From QuerySet's documentation:
bool(). Testing a QuerySet in a boolean context, such as using bool(),
or, and or an if statement, will cause the query to be executed. If
there is at least one result, the QuerySet is True, otherwise False.
For example:
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
You can add one query for query:
For example:
query = query.append(Q(name__isnull=True)) or something that always False
Instead of trying to do it one piece of code:
results = results.filter(
reduce(or_, (Q(name__icontains=itm.strip()) for itm in query))
)
I iterated over each item in query and checked if it exists
for each in query:
r = results.filter(name__icontains=each)
if r.exists() == False:
Do something with each
Though not as efficient as I'd like, it solves the problem for now

django - queryset.last() not returning right order / last record

Writing code that is generating JSON. The last section of JSON has to be terminated by a ",", so in the code I have:
-- Define a queryset to retrieve distinct values of the database field:
databases_in_workload = DatabaseObjectsWorkload.objects.filter(workload=migration.workload_id).values_list('database_object__database', flat=True).distinct()
-- Then I cycle over it:
for database_wk in databases_in_workload:
... do something
if not (database_wk == databases_in_workload.last()):
job_json_string = job_json_string + '} ],'
else:
job_json_string = job_json_string + '} ]'
I want the last record to be terminated by a square bracket, the preceding by a comma. But instead, the opposite is happening.
I also looked at the database table content. The values I have for "database_wk" are user02 (for the records with a lower value of primary key) and user01 (for the records with the higher value of pk in the DB). The order (if user01 is first or last) really doesn't matter, as long as the last record is correctly identified by last() - so if I have user02, user01 in the query set iterations, I expect last() to return user01. However - this is not working correctly.
What is strange is that if in the database (Postgres) order is changed (first have user01, then user02 ordered by primary key values) then the "if" code above works, but in my situation last() seems to be returning the first record, not the last. It's as if there is one order in the database, another in the query set, and last() is taking the database order... Anybody encountered/solved this issue before? Alternatively - any other method for identifying the last record in a query set (other than last()) which I could try would also help. Many thanks in advance!
The reason is behaving the way it does is because there is no ordering specified. Try using order_by. REF
From: queryset.first()
If the QuerySet has no ordering defined, then the queryset is automatically ordered by the primary key
From: queryset.last()
Works like first(), but returns the last object in the queryset.
If you don't want to use order_by then try using queryset.latest()

How to get the value of the counter of an AutoField?

How to get the value of the counter of an AutoField, such as the usual id field of most models?
At the moment, I do:
MyModel.objects.latest('id').id
But that does not work when all the objects have been deleted from the database.
Of course, a database-agnostic answer would be best.
EDIT
The accepted answer in Model next available primary key is not very relevant to my question, as I do not intend to use the counter value to create a new object. Also I don't mind if the value I get is not super accurate.
Background.
AFAIK there isn't a database agnostic query. Different databases handle auto increment differently and there rarely is a use case for django to find out what the next possible auto increment ID is.
To elaborate further, in postgresql you could do select nextval('my_sequence') while in mysql you would need to use the last_insert_id() but what this returns is the ID for the last insert and not the next one these two may actually be very different! To get the actual value you would need to use 'SHOW TABLE STATUS'
Solution.
Create a record, save it, inspect it's ID and delete it.
This will change the next id but you have indicated that you need only an approximation.
The alternative is to do a manual transaction with a rollback. This too would alter the next id in case of mysql.
from django.db import transaction
#transaction.atomic
def find_next_val(mymodel):
try:
# ...
obj = mymoel.objects.create(....)
print obj.id
raise IntegrityError
except IntegrityError:
pass