Skip to content

Commit

Permalink
feat(ISSUE-8): Use Django's form and hide with css
Browse files Browse the repository at this point in the history
  • Loading branch information
Thu Trang Pham committed Feb 21, 2021
1 parent 9aeb799 commit b1c9324
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 130 deletions.
36 changes: 6 additions & 30 deletions admin_confirm/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,35 +137,9 @@ def _get_changed_data(

return changed_data

def _get_form_data(self, request):
"""
Parses the request post params into a format that can be used for the hidden form on the
change confirmation page.
"""
query_dict = request.POST.copy()
# Note: Do not use QueryDict.get(), it returns only the last value for multivalues

for key in SAVE_ACTIONS + [
"_confirm_change",
"_confirm_add",
"csrfmiddlewaretoken",
]:
if query_dict.get(key):
query_dict.pop(key)

form_data = []
for k, v in query_dict.lists():
if isinstance(v, list):
for value in v:
form_data.append((k, value))
else:
form_data.append((k, v))

# form_data = [(k, v) for k, v in form_data.lists()]
return form_data

def _change_confirmation_view(self, request, object_id, form_url, extra_context):
# This code is taken from super()._changeform_view
# https://github.com/django/django/blob/master/django/contrib/admin/options.py#L1575-L1592
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
if to_field and not self.to_field_allowed(request, to_field):
raise DisallowedModelAdminToField(
Expand Down Expand Up @@ -195,8 +169,12 @@ def _change_confirmation_view(self, request, object_id, form_url, extra_context)
)

form = ModelForm(request.POST, request.FILES, obj)
# Note to self: For inline instances see:
# https://github.com/django/django/blob/master/django/contrib/admin/options.py#L1582

# End code from super()._changeform_view

# Get changed data to show on confirmation
changed_data = self._get_changed_data(form, model, obj, add)

changed_confirmation_fields = set(
Expand All @@ -206,8 +184,6 @@ def _change_confirmation_view(self, request, object_id, form_url, extra_context)
# No confirmation required for changed fields, continue to save
return super()._changeform_view(request, object_id, form_url, extra_context)

# Parse raw form data from POST
form_data = self._get_form_data(request)
# Parse the original save action from request
save_action = None
for key in request.POST.keys():
Expand All @@ -227,10 +203,10 @@ def _change_confirmation_view(self, request, object_id, form_url, extra_context)
"app_label": opts.app_label,
"model_name": opts.model_name,
"opts": opts,
"form_data": form_data,
"changed_data": changed_data,
"add": add,
"submit_name": save_action,
"form": form,
**(extra_context or {}),
}
return self.render_change_confirmation(request, context)
Expand Down
28 changes: 16 additions & 12 deletions admin_confirm/static/admin/css/confirmation.css
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
.submit-row a.cancel-link {
display: block;
background: #ba2121;
border-radius: 4px;
padding: 10px 15px;
height: 15px;
line-height: 15px;
color: #fff;
display: block;
background: #ba2121;
border-radius: 4px;
padding: 10px 15px;
height: 15px;
line-height: 15px;
color: #fff;
}

.submit-row a.cancel-link:focus,
.submit-row a.cancel-link:hover,
.submit-row a.cancel-link:active {
background: #a41515;
background: #a41515;
}

.changed-data table {
width: 100%;
width: 100%;
}
.changed-data th,
.changed-data td {
width: 30%;
white-space: nowrap;
}
width: 30%;
white-space: nowrap;
}

.hidden {
display: none;
}
7 changes: 3 additions & 4 deletions admin_confirm/templates/admin/change_confirmation.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@
{% include "admin/change_data.html" %}
<form method="post" action="{% url opts|admin_urlname:'change' object_id|admin_urlquote %}">{% csrf_token %}
{% endif %}

{% for key, value in form_data %}
<input type="hidden" name="{{ key }}" value="{{value}}">
{% endfor %}
<div class=hidden>
{{ form }}
</div>
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
<div class="submit-row">
Expand Down
40 changes: 40 additions & 0 deletions admin_confirm/tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User


class ConfirmAdminTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
username="super", email="super@email.org", password="pass"
)

def setUp(self):
self.client.force_login(self.superuser)
self.factory = RequestFactory()

def _assertManyToManyFormHtml(self, rendered_content, options, selected_ids):
# Form data should be embedded and hidden on confirmation page
# Should have the correct ManyToMany options selected
for option in options:
self.assertIn(
f'<option value="{option.id}"{" selected" if option.id in selected_ids else ""}>{str(option)}</option>',
rendered_content,
)
# ManyToManyField should be embedded
self.assertIn("related-widget-wrapper", rendered_content)

def _assertSubmitHtml(self, rendered_content, save_action="_save"):
# Submit should conserve the save action
self.assertIn(
f'<input type="submit" value="Yes, I’m sure" name="{save_action}">',
rendered_content,
)
# There should not be _confirm_add or _confirm_change sent in the form on confirmaiton page
self.assertNotIn("_confirm_add", rendered_content)
self.assertNotIn("_confirm_change", rendered_content)

def _assertSimpleFieldFormHtml(self, rendered_content, fields):
for k, v in fields.items():
self.assertIn(f'name="{k}"', rendered_content)
self.assertIn(f'value="{v}"', rendered_content)
47 changes: 22 additions & 25 deletions admin_confirm/tests/test_confirm_change_and_add.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.contrib.admin.options import TO_FIELD_VAR
from django.http import HttpResponseForbidden, HttpResponseBadRequest
from django.urls import reverse


from admin_confirm.tests.helpers import ConfirmAdminTestCase
from tests.market.admin import ItemAdmin, InventoryAdmin
from tests.market.models import Item, Inventory
from tests.factories import ItemFactory, ShopFactory, InventoryFactory


class TestConfirmChangeAndAdd(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
username="super", email="super@email.org", password="pass"
)

def setUp(self):
self.client.force_login(self.superuser)
self.factory = RequestFactory()

class TestConfirmChangeAndAdd(ConfirmAdminTestCase):
def test_get_add_without_confirm_add(self):
response = self.client.get(reverse("admin:market_item_add"))
self.assertFalse(response.context_data.get("confirm_add"))
Expand Down Expand Up @@ -53,7 +42,13 @@ def test_post_add_without_confirm_add(self):
def test_post_add_with_confirm_add(self):
item = ItemFactory()
shop = ShopFactory()
data = {"shop": shop.id, "item": item.id, "quantity": 5, "_confirm_add": True}
data = {
"shop": shop.id,
"item": item.id,
"quantity": 5,
"_confirm_add": True,
"_continue": True,
}
response = self.client.post(reverse("admin:market_inventory_add"), data)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
Expand All @@ -63,12 +58,14 @@ def test_post_add_with_confirm_add(self):
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)

form_data = {"shop": str(shop.id), "item": str(item.id), "quantity": str(5)}
for k, v in form_data.items():
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)
self._assertSimpleFieldFormHtml(
rendered_content=response.rendered_content, fields=form_data
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)

# Should not have been added yet
self.assertEqual(Inventory.objects.count(), 0)
Expand Down Expand Up @@ -96,14 +93,14 @@ def test_post_change_with_confirm_change(self):
form_data = {
"name": "name",
"price": str(2.0),
"id": str(item.id),
# "id": str(item.id),
"currency": Item.VALID_CURRENCIES[0][0],
}
for k, v in form_data.items():
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)

self._assertSimpleFieldFormHtml(
rendered_content=response.rendered_content, fields=form_data
)
self._assertSubmitHtml(rendered_content=response.rendered_content)

# Hasn't changed item yet
item.refresh_from_db()
Expand Down
77 changes: 18 additions & 59 deletions admin_confirm/tests/test_confirm_change_and_add_m2m_field.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
from django.urls import reverse


from admin_confirm.tests.helpers import ConfirmAdminTestCase
from tests.market.admin import ShoppingMallAdmin
from tests.market.models import ShoppingMall
from tests.factories import ShopFactory


class TestConfirmChangeAndAddM2MField(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
username="super", email="super@email.org", password="pass"
)

def setUp(self):
self.client.force_login(self.superuser)
self.factory = RequestFactory()

class TestConfirmChangeAndAddM2MField(ConfirmAdminTestCase):
def test_post_add_without_confirm_add_m2m(self):
shops = [ShopFactory() for i in range(3)]

Expand Down Expand Up @@ -50,19 +38,13 @@ def test_post_add_with_confirm_add_m2m(self):
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
form_data = [("name", "name")] + [("shops", s.id) for s in shops]
for k, v in form_data:
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)
# Submit should conserve the save action
self.assertIn(
'<input type="submit" value="Yes, I’m sure" name="_save">',
response.rendered_content,

self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
# There should not be _confirm_add sent in the form on confirmaiton page
self.assertNotIn("_confirm_add", response.rendered_content)
self._assertSubmitHtml(rendered_content=response.rendered_content)

# Should not have been added yet
self.assertEqual(ShoppingMall.objects.count(), 0)
Expand All @@ -84,7 +66,7 @@ def test_m2m_field_post_change_with_confirm_change(self):
# Currently ShoppingMall configured with confirmation_fields = ['name']
data = {
"name": "Not My Mall",
"shops": ["1", "2"],
"shops": [1, 2],
"id": shopping_mall.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
Expand All @@ -101,25 +83,15 @@ def test_m2m_field_post_change_with_confirm_change(self):
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
form_data = {
"name": "Not My Mall",
"shops": "1",
"shops": "2",
"id": str(shopping_mall.id),
}

for k, v in form_data.items():
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)
# Submit should conserve the save action
self.assertIn(
'<input type="submit" value="Yes, I’m sure" name="_continue">',
response.rendered_content,
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# There should not be _confirm_change sent in the form on confirmaiton page
self.assertNotIn("_confirm_change", response.rendered_content)

# Hasn't changed item yet
shopping_mall.refresh_from_db()
Expand Down Expand Up @@ -148,7 +120,7 @@ def test_m2m_field_post_change_with_confirm_change_multiple_selected(self):
# Currently ShoppingMall configured with confirmation_fields = ['name']
data = {
"name": "Not My Mall",
"shops": ["1", "2", "3"],
"shops": [1, 2, 3],
"id": shopping_mall.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
Expand All @@ -165,19 +137,6 @@ def test_m2m_field_post_change_with_confirm_change_multiple_selected(self):
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
form_data = [
("name", "Not My Mall"),
("shops", "1"),
("shops", "2"),
("shops", "3"),
("id", str(shopping_mall.id)),
]

for k, v in form_data:
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)

# Hasn't changed item yet
shopping_mall.refresh_from_db()
Expand Down Expand Up @@ -209,7 +168,7 @@ def test_form_invalid_m2m_value(self):
data = {
"id": shopping_mall.id,
"name": "name",
"shops": ["1", "2", "3"], # These shops don't exist
"shops": [1, 2, 3], # These shops don't exist
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
}
Expand Down

0 comments on commit b1c9324

Please sign in to comment.