Forms + clean_XX + async

Hello all,

I was wondering what the best way is when using forms in an async context. For example:

class View(FormView):
    
    async def post(self, request, *args, **kwargs):
        form = self.get_form()
        kwargs.update({
            'form': form,
        })
        if form.is_valid():
            return await self.form_valid(**kwargs)
        else:
            return await self.form_invalid(**kwargs)

     async def form_valid(self, *kwargs):
        form = kwargs.get('form')
        email = form.cleaned_data('email')
        # value: <coroutine object SyncToAsync.__call__ at 0x7f3530debe20>
        # do stuff

and the simple form:

class Form():
    email = forms.EmailField(
        required=True,
        label='Email',
        initial='',
        max_length=255,
        widget=forms.EmailInput(
            attrs={
                'class': 'fc',
                'placeholder': 'Enter your email',
                'spellcheck': 'false',
                'autocorrect': 'off',
                'autofocus': '',
            }
        )
    )

    @sync_to_async
    def clean_email(self):
        email = self.cleaned_data.get('email', None)
        try:
            User.objects.only('id').get(email=email)
            raise forms.ValidationError(f'There was a problem creating your account. Please try again')
        except User.DoesNotExist:
            pass

        return email

     def clean(self):
        cleaned_data = self.cleaned_data

        return cleaned_data

again, the value returned is <coroutine object SyncToAsync.__call__ at 0x7f3530deb1c0> and not a string :slight_smile:

What am I doing wrong?
. forgot to “async” some other methods in the form validation chain?
. something else?

Thanks

The cleaned_data attribute in a form is a dict, not a function.

zomg.

.get… :Facepalm:

Hum… still. It was a typo in the code above.

Am still getting:


'coroutine' object has no attribute 'get'
<snip>
email=form.cleaned_data.get('email'),

We’re probably going to need to see more of both the view and the form.

You might try awaiting the get function call.

sure thing, I have updated the original post.

<conjecture>
The form.is_valid() function is going to call the full_clean process if the form hasn’t already been validated.

Since full_clean is going to call clean_email, which is to be called from an async context, then you will need to await your call to is_valid.

Another way to do this would be to add:
await form.full_clean() before the if form.is_valid() line.
<conjecture>
Disclaimer: I don’t have any way to check this at the moment, I’m winging this.

ok well I went another way, still async, but could not get it to work with clean_XX methods in the form.

here goes:

class Form(forms.Form):

    email = forms.EmailField(
        required=True,
        label='Email',
        initial='',
        max_length=255,
        widget=forms.EmailInput(
            attrs={
                'class': 'fc',
                'placeholder': 'Enter your email',
                'spellcheck': 'false',
                'autocorrect': 'off',
                'autofocus': '',
            }
        )
    )
class View(FormView):
    async def form_valid(self, **kwargs):
        form = kwargs.get('form', None)

        email = form.cleaned_data.get('email')

        try:
            user = await User.objects.aget(
                email=email
            )
            form.add_error('email', 'There was a problem creating your account. Please try again.')
            return await self.form_invalid(**kwargs)
        except User.DoesNotExist:
            pass

        user = await User.objects.acreate(
            email=email,
        )
        user.set_password('xxx')
        await user.asave()

        await alogin(self.request, user)

        return HttpResponseRedirect(self.get_success_url())

I dont particularly like the shifting of the form field validation(s) in the view but now at least it (seems to) works fine.

Any better ways?