Reputation: 2185
I'm trying to convert my existing Django 4.1 app to async due to performance reasons. It's more of a struggle than I initially anticipated.
Below is some test code:
async def diagrams(request):
tests = await sync_to_async(list)(Test.objects.filter(name='Test 1'))
print(tests)
return render(request, 'analyticsApp/test.html')
class Test2(models.Model):
name = models.CharField(max_length=50, default='', blank=True)
def __str__(self):
return self.name
class Test(models.Model):
name = models.CharField(max_length=50, default='', blank=True)
testForeignKey = models.ForeignKey(Test2, editable=True, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
# Need to get foreign key here in async way but this function cannot be async ??
return self.name + '_' + self.testForeignKey.name
So I kind of figured out how to "filter" objects using async_to_async. However, a problem that I'm struggling to solve is using __str__
in a Model. All of my models use __str__
to give accurate string depcitions of the model. It seems like this cannot be done ? I tried to convert def __str__
to async def __str__
but django is not awaiting this when it is being called so it causes problems.
Any idea how to handle this ?
Upvotes: 0
Views: 518
Reputation: 146
I think your problem here is the print(tests)
That's when the query gets evaluated and at that point __str__
gets called to print the list of tests. Simply put, if you don't print them __str__
will never get called in an async context!
Also the .filter just prepares the sql statement and there's no need to wrap it in the sync_to_async unless you really need a list there which will cause it to evaluate. In this example you don't seem to need it as you can pass a queryset directly to your template for rendering and it will be evaluated there. You can simply do this:
tests = Test.objects.filter(name='Test 1')
return render(request, 'analyticsApp/test.html', { 'tests':tests })
If you absolutely need to print them then you simply wrap the print in a sync_to_async
await sync_to_async(print)(tests)
Or if you need to print them as a list then
test_list = await sync_to_async(list)(tests)
await sync_to_async(print)(test_list)
Print needs to be sync to access the . notation of accessing related fields. Same stands for using the dot notation to access related fields in your template. If you are then wrap the render in a sync_to_async and await it.
return await sync_to_async(render)(request, 'analyticsApp/test.html', { 'tests':tests })
All you manage with select_related is to cause the evaluation of the related fields to happen at the same time as the creation of the list in sync context and that's why it works with the dot notation in your template or with print.
Upvotes: 0
Reputation: 445
I have't used much the async support but as a work around you can use a select_related
async def diagrams(request):
tests = await sync_to_async(list)(
Test.objects.filter(name='Test 1')
.select_related('testForeignKey')
)
print(tests)
return render(request, 'analyticsApp/test.html')
Upvotes: 1
Reputation: 3350
Probably the async don't increase your performance.
In your code you work don't correct with async. Str should work with existing data, without ask to database. Therefore you should receive needed data before instance init.
In your case you can made:
obj = Test.objects.select_related("testForeignKey").get(**something)
Also you can use defer/only. The query works async, after that you get your Str(object) normally.
Upvotes: 1