Skip to content

Commit 9a00741

Browse files
authored
Merge pull request #1 from amirreza8002/form
reducing the need to use sync_to_async on model forms
2 parents f223c77 + 0d5431b commit 9a00741

File tree

3 files changed

+84
-38
lines changed

3 files changed

+84
-38
lines changed

django_async_extensions/aforms/models.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,26 @@
44

55
from django.forms.models import ModelForm
66

7+
from django_async_extensions.aforms.utils import AsyncRenderableFormMixin
8+
9+
10+
class AsyncModelForm(AsyncRenderableFormMixin, ModelForm):
11+
@classmethod
12+
async def from_async(cls, *args, **kwargs):
13+
return await sync_to_async(cls)(*args, **kwargs)
14+
15+
@property
16+
async def aerrors(self):
17+
if self._errors is None:
18+
await self.afull_clean()
19+
return self._errors
20+
21+
async def ais_valid(self):
22+
return self.is_bound and not await self.aerrors
23+
24+
async def afull_clean(self):
25+
return await sync_to_async(self.full_clean)()
726

8-
class AsyncModelForm(ModelForm):
927
async def _asave_m2m(self):
1028
"""
1129
Save the many-to-many fields and generic relations for this form.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from asgiref.sync import sync_to_async
2+
3+
from django.utils.safestring import mark_safe
4+
5+
6+
class AsyncRenderableMixin:
7+
async def arender(self, template_name=None, context=None, renderer=None):
8+
renderer = renderer or self.renderer
9+
template = template_name or self.template_name
10+
context = context or self.get_context()
11+
return mark_safe( # noqa:S308
12+
await sync_to_async(renderer.render)(template, context)
13+
)
14+
15+
16+
class AsyncRenderableFormMixin(AsyncRenderableMixin):
17+
async def aas_p(self):
18+
"""Render as <p> elements."""
19+
return await self.arender(self.template_name_p)
20+
21+
async def aas_table(self):
22+
"""Render as <tr> elements excluding the surrounding <table> tag."""
23+
return await self.arender(self.template_name_table)
24+
25+
async def aas_ul(self):
26+
"""Render as <li> elements excluding the surrounding <ul> tag."""
27+
return await self.arender(self.template_name_ul)
28+
29+
async def aas_div(self):
30+
"""Render as <div> elements."""
31+
return await self.arender(self.template_name_div)

tests/test_model_forms/test_model_form.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def __init__(self, *args, **kwargs):
310310
assert f1.is_valid()
311311

312312
f2 = FormForTestingIsValid(data2)
313-
assert await sync_to_async(f2.is_valid)()
313+
assert await f2.ais_valid()
314314

315315
obj = await f2.asave()
316316
assert obj.character == char
@@ -965,25 +965,25 @@ def setup(self):
965965

966966
async def test_simple_unique(self):
967967
form = ProductForm({"slug": "teddy-bear-blue"})
968-
assert await sync_to_async(form.is_valid)()
968+
assert await form.ais_valid()
969969
obj = await form.asave()
970970
form = ProductForm({"slug": "teddy-bear-blue"})
971-
await sync_to_async(form.full_clean)()
972-
assert len(form.errors) == 1
973-
assert form.errors["slug"] == ["Product with this Slug already exists."]
971+
errors = await form.aerrors
972+
assert len(errors) == 1
973+
assert errors["slug"] == ["Product with this Slug already exists."]
974974
form = ProductForm({"slug": "teddy-bear-blue"}, instance=obj)
975-
assert await sync_to_async(form.is_valid)()
975+
assert await form.ais_valid()
976976

977977
async def test_unique_together(self):
978978
"""ModelForm test of unique_together constraint"""
979979
form = PriceForm({"price": "6.00", "quantity": "1"})
980-
assert await sync_to_async(form.is_valid)()
980+
assert await form.ais_valid()
981981
await form.asave()
982982
form = PriceForm({"price": "6.00", "quantity": "1"})
983-
assert await sync_to_async(form.is_valid)() is False
984-
await sync_to_async(form.full_clean)()
985-
assert len(form.errors) == 1
986-
assert form.errors["__all__"] == [
983+
assert await form.ais_valid() is False
984+
errors = await form.aerrors
985+
assert len(errors) == 1
986+
assert errors["__all__"] == [
987987
"Price with this Price and Quantity already exists."
988988
]
989989

@@ -1044,15 +1044,13 @@ class Meta:
10441044
async def test_unique_null(self):
10451045
title = "I May Be Wrong But I Doubt It"
10461046
form = BookForm({"title": title, "author": self.writer.pk})
1047-
assert await sync_to_async(form.is_valid)()
1047+
assert await form.ais_valid()
10481048
await form.asave()
10491049
form = BookForm({"title": title, "author": self.writer.pk})
1050-
assert await sync_to_async(form.is_valid)() is False
1051-
await sync_to_async(form.full_clean)()
1052-
assert len(form.errors) == 1
1053-
assert form.errors["__all__"] == [
1054-
"Book with this Title and Author already exists."
1055-
]
1050+
assert await form.ais_valid() is False
1051+
errors = await form.aerrors
1052+
assert len(errors) == 1
1053+
assert errors["__all__"] == ["Book with this Title and Author already exists."]
10561054
form = BookForm({"title": title})
10571055
assert form.is_valid()
10581056
await form.asave()
@@ -1079,12 +1077,12 @@ def test_inherited_unique(self):
10791077
async def test_inherited_unique_together(self):
10801078
title = "Boss"
10811079
form = BookForm({"title": title, "author": self.writer.pk})
1082-
assert await sync_to_async(form.is_valid)()
1080+
assert await form.ais_valid()
10831081
await form.asave()
10841082
form = DerivedBookForm(
10851083
{"title": title, "author": self.writer.pk, "isbn": "12345"}
10861084
)
1087-
assert await sync_to_async(form.is_valid)() is False
1085+
assert await form.ais_valid() is False
10881086
assert len(form.errors) == 1
10891087
assert form.errors["__all__"] == [
10901088
"Book with this Title and Author already exists."
@@ -1134,10 +1132,10 @@ def test_explicitpk_unspecified(self):
11341132
async def test_explicitpk_unique(self):
11351133
"""Ensure keys and blank character strings are tested for uniqueness."""
11361134
form = ExplicitPKForm({"key": "key1", "desc": ""})
1137-
assert await sync_to_async(form.is_valid)()
1135+
assert await form.ais_valid()
11381136
await form.asave()
11391137
form = ExplicitPKForm({"key": "key1", "desc": ""})
1140-
assert await sync_to_async(form.is_valid)() is False
1138+
assert await form.ais_valid() is False
11411139
if connection.features.interprets_empty_strings_as_nulls:
11421140
assert len(form.errors) == 1
11431141
assert form.errors["key"] == ["Explicit pk with this Key already exists."]
@@ -1397,7 +1395,7 @@ async def test_initial_values(self):
13971395
},
13981396
)
13991397
assertHTMLEqual(
1400-
await sync_to_async(f.as_ul)(),
1398+
await f.aas_ul(),
14011399
"""
14021400
<li>Headline:
14031401
<input type="text" name="headline" value="Your headline here" maxlength="50"
@@ -1446,9 +1444,9 @@ async def test_initial_values(self):
14461444
)
14471445
art_id_1 = art.id
14481446

1449-
f = await sync_to_async(ArticleForm)(auto_id=False, instance=art)
1447+
f = await ArticleForm.from_async(auto_id=False, instance=art)
14501448
assertHTMLEqual(
1451-
await sync_to_async(f.as_ul)(),
1449+
await f.aas_ul(),
14521450
"""
14531451
<li>Headline:
14541452
<input type="text" name="headline" value="Test article" maxlength="50"
@@ -1481,7 +1479,7 @@ async def test_initial_values(self):
14811479
% (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk),
14821480
)
14831481

1484-
f = await sync_to_async(ArticleForm)(
1482+
f = await ArticleForm.from_async(
14851483
{
14861484
"headline": "Test headline",
14871485
"slug": "test-headline",
@@ -1491,8 +1489,7 @@ async def test_initial_values(self):
14911489
},
14921490
instance=art,
14931491
)
1494-
await sync_to_async(f.full_clean)()
1495-
assert f.errors == {}
1492+
assert await f.aerrors == {}
14961493
assert f.is_valid()
14971494
test_art = await f.asave()
14981495
assert test_art.id == art_id_1
@@ -1521,7 +1518,7 @@ def formfield_for_dbfield(db_field, **kwargs):
15211518
)
15221519
form = ModelForm()
15231520
assertHTMLEqual(
1524-
await sync_to_async(form.as_ul)(),
1521+
await form.aas_ul(),
15251522
"""<li><label for="id_headline">Headline:</label>
15261523
<input id="id_headline" type="text" name="headline" maxlength="50" required></li>
15271524
<li><label for="id_categories">Categories:</label>
@@ -1640,9 +1637,9 @@ async def test_multi_fields(self):
16401637
)
16411638
await new_art.categories.aadd(await Category.objects.aget(name="Entertainment"))
16421639
assert [art async for art in new_art.categories.all()] == [self.c1]
1643-
f = await sync_to_async(ArticleForm)(auto_id=False, instance=new_art)
1640+
f = await ArticleForm.from_async(auto_id=False, instance=new_art)
16441641
assertHTMLEqual(
1645-
await sync_to_async(f.as_ul)(),
1642+
await f.aas_ul(),
16461643
"""
16471644
<li>Headline:
16481645
<input type="text" name="headline" value="New headline" maxlength="50"
@@ -1760,7 +1757,7 @@ async def test_m2m_editing(self):
17601757
# Now, submit form data with no categories. This deletes the existing
17611758
# categories.
17621759
form_data["categories"] = []
1763-
f = await sync_to_async(ArticleForm)(form_data, instance=new_art)
1760+
f = await ArticleForm.from_async(form_data, instance=new_art)
17641761
new_art = await f.asave()
17651762
assert new_art.id == art_id_1
17661763
new_art = await Article.objects.aget(id=art_id_1)
@@ -1826,7 +1823,7 @@ async def test_runtime_choicefield_populated(self):
18261823
await self.create_basic_data()
18271824
f = ArticleForm(auto_id=False)
18281825
assertHTMLEqual(
1829-
await sync_to_async(f.as_ul)(),
1826+
await f.aas_ul(),
18301827
'<li>Headline: <input type="text" name="headline" maxlength="50" required>'
18311828
"</li>"
18321829
'<li>Slug: <input type="text" name="slug" maxlength="50" required></li>'
@@ -1855,7 +1852,7 @@ async def test_runtime_choicefield_populated(self):
18551852
c4 = await Category.objects.acreate(name="Fourth", url="4th")
18561853
w_bernstein = await Writer.objects.acreate(name="Carl Bernstein")
18571854
assertHTMLEqual(
1858-
await sync_to_async(f.as_ul)(),
1855+
await f.aas_ul(),
18591856
'<li>Headline: <input type="text" name="headline" maxlength="50" required>'
18601857
"</li>"
18611858
'<li>Slug: <input type="text" name="slug" maxlength="50" required></li>'
@@ -1984,7 +1981,7 @@ def __init__(self, *args, **kwargs):
19841981
"article": "lorem ipsum",
19851982
}
19861983
form = MyForm(data)
1987-
assert await sync_to_async(form.is_valid)()
1984+
assert await form.ais_valid()
19881985
article = await form.asave()
19891986
assert article.writer == w
19901987

@@ -2264,7 +2261,7 @@ class Meta:
22642261

22652262
form = WriterProfileForm()
22662263
assertHTMLEqual(
2267-
await sync_to_async(form.as_p)(),
2264+
await form.aas_p(),
22682265
"""
22692266
<p><label for="id_writer">Writer:</label>
22702267
<select name="writer" id="id_writer" required>
@@ -2291,7 +2288,7 @@ class Meta:
22912288

22922289
form = WriterProfileForm(instance=instance)
22932290
assertHTMLEqual(
2294-
await sync_to_async(form.as_p)(),
2291+
await form.aas_p(),
22952292
"""
22962293
<p><label for="id_writer">Writer:</label>
22972294
<select name="writer" id="id_writer" required>

0 commit comments

Comments
 (0)