ceds
ceds

Reputation: 2185

Django Async View - Model __str__

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

Answers (3)

Giorgos Gernas
Giorgos Gernas

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

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

Maxim Danilov
Maxim Danilov

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

Related Questions