Idiomatic/fast Django ORM check for existence on mysql/postgres - python

If I want to check for the existence and if possible retrieve an object, which of the following methods is faster? More idiomatic? And why? If not either of the two examples I list, how else would one go about doing this?
if Object.objects.get(**kwargs).exists():
my_object = Object.objects.get(**kwargs)
my_object = Object.objects.filter(**kwargs)
if my_object:
my_object = my_object[0]
If relevant, I care about mysql and postgres for this.

Why not do this in a try/except block to avoid the multiple queries / query then an if?
try:
obj = Object.objects.get(**kwargs)
except Object.DoesNotExist:
pass
Just add your else logic under the except.

django provides a pretty good overview of exists
Using your first example it will do the query two times, according to the documentation:
if 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 (one query for the existence check plus an
extra one to later retrieve the results) than simply using
bool(some_queryset), which retrieves the results and then checks if
any were returned.
So if you're going to be using the object, after checking for existance, the docs suggest just using it and forcing evaluation 1 time using
if my_object:
pass

Related

Using transactions in Django

I'm using django 1.11 on python 3.7
In a method I want to execute some database queries, mainly updating links between objects and I want to use this method to perform a check on what needs to be updated in a sync-operation. The following is an implementation:
results = {}
with transaction.atomic():
sid = transaction.savepoint()
for speaker_user in speaker_users:
# here my code checks all sorts of things, updates the database with
# new connections between objects and stores them all in the
# results-dict, using a lot of code in other classes which
# I really dont want to change for this operation
if sync_test_only:
transaction.savepoint_rollback(sid)
else:
transaction.savepoint_commit(sid)
return results
This snippet is used in a method with the sync_test_only parameter that should only fill the results-dict without doing the database changes that go along with it.
So this method can be used to do the actual work, when sync_test_only is False, and also only report back the work to-be-done, when sync_test_only is True
Is this what the transaction.atomic() is designed for? Does this actually work in my use-case? If not, what would be a better way to achieve this behaviour?
Another option would be to use exceptions, like the docs suggest (read the part under the title "You may need to manually revert model state when rolling back a transaction"):
class MyException(Exception):
pass
def f(do_commit=False):
results = {}
try:
with transaction.atomic():
for speaker_user in speaker_users:
pass
if not do_commit:
raise MyException
except MyException:
# do nothing here
pass
return results
I suggest creating a custom exception so you don't accidently catch something that was raised somewhere else in the code.

Django How to make only one query with queryset exists method?

I try to understand Django documentation for queryset exists method
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 (one query for the existence check plus an
extra one to later retrieve the results) than simply using
bool(some_queryset), which retrieves the results and then checks if
any were returned.
What I'm doing:
if queryset.exists():
do_something()
for element in queryset:
do_something_else(element)
So I'm doing more overall work than just using bool(some_queryset)
Does this code makes only one query?
if bool(queryset):
do_something()
for element in queryset:
do_something_else(element)
If yes where python puts the results ? In queryset variable ?
Thank you
From the .exists() docs itself:
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 (one query for the
existence check plus an extra one to later retrieve the results) than
simply using bool(some_queryset), which retrieves the results and
then checks if any were returned.
The results of an already evaluated queryset are cached by Django. So, whenever the data is required from the queryset the cached results are used.
Related docs: Caching and QuerySets
It is quite easy to check number of queries with assertNumQueries:
https://docs.djangoproject.com/en/1.3/topics/testing/#django.test.TestCase.assertNumQueries
In your case:
with self.assertNumQueries(1):
if bool(queryset):
do_something()
for element in queryset:
do_something_else(element)

How does the get_list tastypie function work?

I'm trying to use the get_list tastypie function but I can't make it work. I've looked for documentation about that but I can't find it.
Whatever, I've a list of item ids and an ItemResource. I'm trying to return a list of serialized objects.
So I just want to do something like that :
item_resource = ItemResource()
item_ids = my_item_id_list
return item_resource.get_list(request, id=item_ids)
But of course it's not working.
What would be the correct syntax to do that ?
Thx !
Unless your ItemResource accepts filters (more here), you have to copy-paste all the stuff from here, lines #1306 - #1313.
The point is that get_list results get filtered only by obj_get_list (initial filters), and apply_filters (request-specific filters) so you have to skip directly to the serialization part (you can include the pagination part, if needed).
This is one of the cases where django-restframework appears to be better than django-tastypie - it refactores serialization out into a separate class, avoiding the code duplication.

Django : Can we use .exclude() on .get() in django querysets

Can we use
MyClass.objects.get(description='hi').exclude(status='unknown')
Your code works as expected if you do the exclude() before the get():
MyClass.objects.exclude(status='unknown').get(description='hi')
As #Burhan Khalid points out, the call to .get will only succeed if the resulting query returns exactly one row.
You could also use the Q object to get specify the filter directly in the .get:
MyClass.objects.get(Q(description='hi') & ~Q(status='unknown'))
Note that the Q object is only necessary because you use a .exclude (and Django's ORM does not have a not equal field lookup so you have to use .exclude).
If your original code had been (note that .exclude has been replaced with .filter):
MyClass.objects.filter(status='unknown').get(description='hi')
... you could simply do:
MyClass.objects.get(status='unknown', description='hi')
You want instead:
MyClass.objects.filter(description='hi').exclude(status='unknown')
.get() will raise MultipleObjectsReturned if your query results in more than one matching set; which is likely to happen considering you are searching on something that isn't a primary key.
Using filter will give you a QuerySet, which you can later chain with other methods or simply step through to get the results.

Duplicate an AppEngine Query object to create variations of a filter without affecting the base query

In my AppEngine project I have a need to use a certain filter as a base then apply various different extra filters to the end, retrieving the different result sets separately. e.g.:
base_query = MyModel.all().filter('mainfilter', 123)
Then I need to use the results of various sub queries separately:
subquery1 = basequery.filter('subfilter1', 'xyz')
#Do something with subquery1 results here
subquery2 = basequery.filter('subfilter2', 'abc')
#Do something with subquery2 results here
Unfortunately 'filter()' affects the state of the basequery Query instance, rather than just returning a modified version. Is there any way to duplicate the Query object and use it as a base? Is there perhaps a standard Python way of duping an object that could be used?
The extra filters are actually applied by the results of different forms dynamically within a wizard, and they use the 'running total' of the query in their branch to assess whether to ask further questions.
Obviously I could pass around a rudimentary stack of filter criteria, but I'd rather use the Query itself if possible, as it adds simplicity and elegance to the solution.
There's no officially approved (Eg, not likely to break) way to do this. Simply creating the query afresh from the parameters when you need it is your best option.
As Nick has said, you better create the query again, but you can still avoid repeating yourself. A good way to do that would be like this:
#inside a request handler
def create_base_query():
return MyModel.all().filter('mainfilter', 123)
subquery1 = create_base_query().filter('subfilter1', 'xyz')
#Do something with subquery1 results here
subquery2 = create_base_query().filter('subfilter2', 'abc')
#Do something with subquery2 results here

Categories

Resources