Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion django_async_extensions/aforms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@

from django.forms.models import ModelForm

from django_async_extensions.aforms.utils import AsyncRenderableFormMixin


class AsyncModelForm(AsyncRenderableFormMixin, ModelForm):
@classmethod
async def from_async(cls, *args, **kwargs):
return await sync_to_async(cls)(*args, **kwargs)

@property
async def aerrors(self):
if self._errors is None:
await self.afull_clean()
return self._errors

async def ais_valid(self):
return self.is_bound and not await self.aerrors

async def afull_clean(self):
return await sync_to_async(self.full_clean)()

class AsyncModelForm(ModelForm):
async def _asave_m2m(self):
"""
Save the many-to-many fields and generic relations for this form.
Expand Down
31 changes: 31 additions & 0 deletions django_async_extensions/aforms/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from asgiref.sync import sync_to_async

from django.utils.safestring import mark_safe


class AsyncRenderableMixin:
async def arender(self, template_name=None, context=None, renderer=None):
renderer = renderer or self.renderer
template = template_name or self.template_name
context = context or self.get_context()
return mark_safe( # noqa:S308
await sync_to_async(renderer.render)(template, context)
)


class AsyncRenderableFormMixin(AsyncRenderableMixin):
async def aas_p(self):
"""Render as <p> elements."""
return await self.arender(self.template_name_p)

async def aas_table(self):
"""Render as <tr> elements excluding the surrounding <table> tag."""
return await self.arender(self.template_name_table)

async def aas_ul(self):
"""Render as <li> elements excluding the surrounding <ul> tag."""
return await self.arender(self.template_name_ul)

async def aas_div(self):
"""Render as <div> elements."""
return await self.arender(self.template_name_div)
71 changes: 34 additions & 37 deletions tests/test_model_forms/test_model_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def __init__(self, *args, **kwargs):
assert f1.is_valid()

f2 = FormForTestingIsValid(data2)
assert await sync_to_async(f2.is_valid)()
assert await f2.ais_valid()

obj = await f2.asave()
assert obj.character == char
Expand Down Expand Up @@ -965,25 +965,25 @@ def setup(self):

async def test_simple_unique(self):
form = ProductForm({"slug": "teddy-bear-blue"})
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
obj = await form.asave()
form = ProductForm({"slug": "teddy-bear-blue"})
await sync_to_async(form.full_clean)()
assert len(form.errors) == 1
assert form.errors["slug"] == ["Product with this Slug already exists."]
errors = await form.aerrors
assert len(errors) == 1
assert errors["slug"] == ["Product with this Slug already exists."]
form = ProductForm({"slug": "teddy-bear-blue"}, instance=obj)
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()

async def test_unique_together(self):
"""ModelForm test of unique_together constraint"""
form = PriceForm({"price": "6.00", "quantity": "1"})
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
await form.asave()
form = PriceForm({"price": "6.00", "quantity": "1"})
assert await sync_to_async(form.is_valid)() is False
await sync_to_async(form.full_clean)()
assert len(form.errors) == 1
assert form.errors["__all__"] == [
assert await form.ais_valid() is False
errors = await form.aerrors
assert len(errors) == 1
assert errors["__all__"] == [
"Price with this Price and Quantity already exists."
]

Expand Down Expand Up @@ -1044,15 +1044,13 @@ class Meta:
async def test_unique_null(self):
title = "I May Be Wrong But I Doubt It"
form = BookForm({"title": title, "author": self.writer.pk})
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
await form.asave()
form = BookForm({"title": title, "author": self.writer.pk})
assert await sync_to_async(form.is_valid)() is False
await sync_to_async(form.full_clean)()
assert len(form.errors) == 1
assert form.errors["__all__"] == [
"Book with this Title and Author already exists."
]
assert await form.ais_valid() is False
errors = await form.aerrors
assert len(errors) == 1
assert errors["__all__"] == ["Book with this Title and Author already exists."]
form = BookForm({"title": title})
assert form.is_valid()
await form.asave()
Expand All @@ -1079,12 +1077,12 @@ def test_inherited_unique(self):
async def test_inherited_unique_together(self):
title = "Boss"
form = BookForm({"title": title, "author": self.writer.pk})
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
await form.asave()
form = DerivedBookForm(
{"title": title, "author": self.writer.pk, "isbn": "12345"}
)
assert await sync_to_async(form.is_valid)() is False
assert await form.ais_valid() is False
assert len(form.errors) == 1
assert form.errors["__all__"] == [
"Book with this Title and Author already exists."
Expand Down Expand Up @@ -1134,10 +1132,10 @@ def test_explicitpk_unspecified(self):
async def test_explicitpk_unique(self):
"""Ensure keys and blank character strings are tested for uniqueness."""
form = ExplicitPKForm({"key": "key1", "desc": ""})
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
await form.asave()
form = ExplicitPKForm({"key": "key1", "desc": ""})
assert await sync_to_async(form.is_valid)() is False
assert await form.ais_valid() is False
if connection.features.interprets_empty_strings_as_nulls:
assert len(form.errors) == 1
assert form.errors["key"] == ["Explicit pk with this Key already exists."]
Expand Down Expand Up @@ -1397,7 +1395,7 @@ async def test_initial_values(self):
},
)
assertHTMLEqual(
await sync_to_async(f.as_ul)(),
await f.aas_ul(),
"""
<li>Headline:
<input type="text" name="headline" value="Your headline here" maxlength="50"
Expand Down Expand Up @@ -1446,9 +1444,9 @@ async def test_initial_values(self):
)
art_id_1 = art.id

f = await sync_to_async(ArticleForm)(auto_id=False, instance=art)
f = await ArticleForm.from_async(auto_id=False, instance=art)
assertHTMLEqual(
await sync_to_async(f.as_ul)(),
await f.aas_ul(),
"""
<li>Headline:
<input type="text" name="headline" value="Test article" maxlength="50"
Expand Down Expand Up @@ -1481,7 +1479,7 @@ async def test_initial_values(self):
% (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk),
)

f = await sync_to_async(ArticleForm)(
f = await ArticleForm.from_async(
{
"headline": "Test headline",
"slug": "test-headline",
Expand All @@ -1491,8 +1489,7 @@ async def test_initial_values(self):
},
instance=art,
)
await sync_to_async(f.full_clean)()
assert f.errors == {}
assert await f.aerrors == {}
assert f.is_valid()
test_art = await f.asave()
assert test_art.id == art_id_1
Expand Down Expand Up @@ -1521,7 +1518,7 @@ def formfield_for_dbfield(db_field, **kwargs):
)
form = ModelForm()
assertHTMLEqual(
await sync_to_async(form.as_ul)(),
await form.aas_ul(),
"""<li><label for="id_headline">Headline:</label>
<input id="id_headline" type="text" name="headline" maxlength="50" required></li>
<li><label for="id_categories">Categories:</label>
Expand Down Expand Up @@ -1640,9 +1637,9 @@ async def test_multi_fields(self):
)
await new_art.categories.aadd(await Category.objects.aget(name="Entertainment"))
assert [art async for art in new_art.categories.all()] == [self.c1]
f = await sync_to_async(ArticleForm)(auto_id=False, instance=new_art)
f = await ArticleForm.from_async(auto_id=False, instance=new_art)
assertHTMLEqual(
await sync_to_async(f.as_ul)(),
await f.aas_ul(),
"""
<li>Headline:
<input type="text" name="headline" value="New headline" maxlength="50"
Expand Down Expand Up @@ -1760,7 +1757,7 @@ async def test_m2m_editing(self):
# Now, submit form data with no categories. This deletes the existing
# categories.
form_data["categories"] = []
f = await sync_to_async(ArticleForm)(form_data, instance=new_art)
f = await ArticleForm.from_async(form_data, instance=new_art)
new_art = await f.asave()
assert new_art.id == art_id_1
new_art = await Article.objects.aget(id=art_id_1)
Expand Down Expand Up @@ -1826,7 +1823,7 @@ async def test_runtime_choicefield_populated(self):
await self.create_basic_data()
f = ArticleForm(auto_id=False)
assertHTMLEqual(
await sync_to_async(f.as_ul)(),
await f.aas_ul(),
'<li>Headline: <input type="text" name="headline" maxlength="50" required>'
"</li>"
'<li>Slug: <input type="text" name="slug" maxlength="50" required></li>'
Expand Down Expand Up @@ -1855,7 +1852,7 @@ async def test_runtime_choicefield_populated(self):
c4 = await Category.objects.acreate(name="Fourth", url="4th")
w_bernstein = await Writer.objects.acreate(name="Carl Bernstein")
assertHTMLEqual(
await sync_to_async(f.as_ul)(),
await f.aas_ul(),
'<li>Headline: <input type="text" name="headline" maxlength="50" required>'
"</li>"
'<li>Slug: <input type="text" name="slug" maxlength="50" required></li>'
Expand Down Expand Up @@ -1984,7 +1981,7 @@ def __init__(self, *args, **kwargs):
"article": "lorem ipsum",
}
form = MyForm(data)
assert await sync_to_async(form.is_valid)()
assert await form.ais_valid()
article = await form.asave()
assert article.writer == w

Expand Down Expand Up @@ -2264,7 +2261,7 @@ class Meta:

form = WriterProfileForm()
assertHTMLEqual(
await sync_to_async(form.as_p)(),
await form.aas_p(),
"""
<p><label for="id_writer">Writer:</label>
<select name="writer" id="id_writer" required>
Expand All @@ -2291,7 +2288,7 @@ class Meta:

form = WriterProfileForm(instance=instance)
assertHTMLEqual(
await sync_to_async(form.as_p)(),
await form.aas_p(),
"""
<p><label for="id_writer">Writer:</label>
<select name="writer" id="id_writer" required>
Expand Down
Loading