fix(MR-16): Use cache for FileField and ImageField (#17)

* Remove casting to list in _get_form_data

* Use cache for most fields and admin form for m2m files

* MR comments/clean up

* Cache should obey exclude and fields

* Some more tests and docs

* Only use cache for image files

* Even more tests and handle save as new

* fix test

* More tests

* minor refactor

* Improve test coverage

* Add no cover for some places

* V0.2.3.dev7

* Adding tests for fieldsets

* Added cache timeout

* Added another test for an edge case

* Fix issue with ManagementForm tampered with

* Update cache to only set when form is_multipart

* Even more testing changes

* Update based on comments on MR and clean up a bit

* make test names better

Co-authored-by: Thu Trang Pham <thu@joinmodernhealth.com>
This commit is contained in:
Thu Trang Pham
2021-02-27 15:39:01 -08:00
committed by GitHub
parent cc36492bfe
commit 06d3e1a208
31 changed files with 3017 additions and 116 deletions
+100
View File
@@ -0,0 +1,100 @@
# Testing Documentation/Notes
[![Coverage Status](https://coveralls.io/repos/github/TrangPham/django-admin-confirm/badge.svg)](https://coveralls.io/github/TrangPham/django-admin-confirm)
Hello, friend! You have found the list of test cases that this package can benefit from.
You seem concerned about the stability and reliability of this package. You're probably wondering if you should include it in your production codebase. Well, although I have tried very hard to get 100% code coverage, there are so many permutations of ModelAdmins in the wild. And I'm only one person.
So if you want to include this package in your production codebase, be aware that AdminConfirmMixin works best with simple unmodified ModelAdmins.
## Save Options
- [x] Save
- [x] Conitnue
- [x] Save As New
- [x] Add another
### Field types
- [x] CharField
- [x] PositiveIntegerField
- [x] DecimalField
- [x] TextField
- [x] ImageField
- [x] FileField
- [x] ManyToManyField
- [x] OneToOneField
- [x] ForeignKey
- [x] Custom Readonly fields
### Options
- [x] .exclude
- [x] .fields
- [x] .readonly_fields
- [x] Actions
### Options to test
- [x] ModelAdmin.fieldsets
- [ ] ModelAdmin.form
- [ ] ModelAdmin.raw_id_fields
- [ ] ModelAdmin.radio_fields
- [ ] ModelAdmin.autocomplete_fields
- [ ] ModelAdmin.prepopulated_fields
## ModelAdmin form template overrides?
https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#custom-template-options
(Maybe??? IDK this is esoteric)
## Function overrides to test
- [ ] .save_model()
- [ ] .get_readonly_fields()
- [ ] .get_fields()
- [ ] .get_excludes()
- [ ] .get_form()
- [ ] .get_autocomplete_fields()
- [ ] .get_prepopulated_fields()
- [x] .get_fieldsets()
- [ ] ModelAdmin.formfield_for_manytomany()
- [ ] ModelAdmin.formfield_for_foreignkey()
- [ ] ModelAdmin.formfield_for_choice_field()
- [ ] ModelAdmin.get_changeform_initial_data()
## Inline instance support??
Confirmation on inline changes is not a current feature of this project.
Confirmation on add/change of ModelAdmin that includes inlines needs to be tested. Use AdminConfirmMixin with ModelAdmin containing inlines at your own risk.
- [ ] .inlines
- [ ] .get_inline_instances()
- [ ] .get_inlines() (New in Django 3.0)
- [ ] .get_formsets_with_inlines()
#### Options for inlines
- [ ] classes of inlines: Tabular, Stacked, etc
- [ ] extra
- [ ] action on the inline: add or change
- [ ] clicking add another on the inline
## IDK if we want to support these
- [ ] .get_changelist_form()
- [ ] ModelAdmin.list_editable
- [ ] ModelAdmin.changelist_view()
- [ ] ModelAdmin.add_view(request, form_url='', extra_context=None)
- [ ] ModelAdmin.change_view(request, object_id, form_url='', extra_context=None)
## More tests for these?
Note: Currently the code always calls super().\_changeform_view(), which would ensure permissions correct as well
- [x] ModelAdmin.has_add_permission
- [x] ModelAdmin.has_change_permission
+20 -2
View File
@@ -1,8 +1,13 @@
from django.core.cache import cache
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
class ConfirmAdminTestCase(TestCase):
class AdminConfirmTestCase(TestCase):
"""
Helper TestCase class and common associated assertions
"""
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
@@ -10,6 +15,7 @@ class ConfirmAdminTestCase(TestCase):
)
def setUp(self):
cache.clear()
self.client.force_login(self.superuser)
self.factory = RequestFactory()
@@ -24,7 +30,9 @@ class ConfirmAdminTestCase(TestCase):
# ManyToManyField should be embedded
self.assertIn("related-widget-wrapper", rendered_content)
def _assertSubmitHtml(self, rendered_content, save_action="_save"):
def _assertSubmitHtml(
self, rendered_content, save_action="_save", multipart_form=False
):
# Submit should conserve the save action
self.assertIn(
f'<input type="submit" value="Yes, Im sure" name="{save_action}">',
@@ -34,6 +42,16 @@ class ConfirmAdminTestCase(TestCase):
self.assertNotIn("_confirm_add", rendered_content)
self.assertNotIn("_confirm_change", rendered_content)
confirmation_received_html = (
'<input type="hidden" name=CONFIRMATION_RECEIVED value="True">'
)
if multipart_form:
# Should have _confirmation_received as a hidden field
self.assertIn(confirmation_received_html, rendered_content)
else:
self.assertNotIn(confirmation_received_html, rendered_content)
def _assertSimpleFieldFormHtml(self, rendered_content, fields):
for k, v in fields.items():
self.assertIn(f'name="{k}"', rendered_content)
@@ -0,0 +1,307 @@
from unittest import mock
from django.core.cache import cache
from admin_confirm.tests.helpers import AdminConfirmTestCase
from tests.market.admin import ShoppingMallAdmin
from tests.market.models import GeneralManager, ShoppingMall, Town
from tests.factories import ShopFactory
from admin_confirm.constants import CACHE_KEYS, CONFIRMATION_RECEIVED
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
class TestAdminOptions(AdminConfirmTestCase):
@mock.patch.object(ShoppingMallAdmin, "confirmation_fields", ["name"])
@mock.patch.object(ShoppingMallAdmin, "fields", ["name", "town"])
def test_change_model_with_m2m_field_without_input_for_m2m_field_should_work(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
data = {
"id": mall.id,
"name": "name",
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# Should not have cached the unsaved obj
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved changes yet
self.assertEqual(ShoppingMall.objects.count(), 1)
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_change"]
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/",
data=confirmation_received_data,
)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved obj
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
# should have updated fields that were in form
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.town, town2)
# should have presevered the fields that are not in form
self.assertEqual(saved_item.general_manager, gm)
for shop in saved_item.shops.all():
self.assertIn(shop, shops)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@mock.patch.object(ShoppingMallAdmin, "confirmation_fields", ["name"])
@mock.patch.object(ShoppingMallAdmin, "exclude", ["shops"])
def test_when_m2m_field_in_exclude_changes_to_field_should_not_be_saved(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
data = {
"id": mall.id,
"name": "name",
"general_manager": gm2.id,
"shops": [1],
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# Should not have cached the unsaved obj
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved changes yet
self.assertEqual(ShoppingMall.objects.count(), 1)
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_change"]
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/",
data=confirmation_received_data,
)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved obj
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
# should have updated fields that were in form
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.town, town2)
self.assertEqual(saved_item.general_manager, gm2)
# should have presevered the fields that are not in form (exclude)
for shop in saved_item.shops.all():
self.assertIn(shop, shops)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@mock.patch.object(ShoppingMallAdmin, "confirmation_fields", ["name"])
@mock.patch.object(ShoppingMallAdmin, "exclude", ["shops", "name"])
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
def test_if_confirmation_fields_in_exclude_should_not_trigger_confirmation(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
data = {
"id": mall.id,
"name": "name",
"general_manager": gm2.id,
"shops": [1],
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should not be shown confirmation page
# SInce we used "Save and Continue", should show change page
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved the non excluded fields
mall.refresh_from_db()
for shop in shops:
self.assertIn(shop, mall.shops.all())
self.assertEqual(mall.name, "mall")
# Should have saved other fields
self.assertEqual(mall.town, town2)
self.assertEqual(mall.general_manager, gm2)
@mock.patch.object(ShoppingMallAdmin, "confirmation_fields", ["name"])
@mock.patch.object(ShoppingMallAdmin, "readonly_fields", ["shops", "name"])
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
def test_if_confirmation_fields_in_readonly_should_not_trigger_confirmation(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
data = {
"id": mall.id,
"name": "name",
"general_manager": gm2.id,
"shops": [1],
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should not be shown confirmation page
# SInce we used "Save and Continue", should show change page
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved the non excluded fields
mall.refresh_from_db()
for shop in shops:
self.assertIn(shop, mall.shops.all())
self.assertEqual(mall.name, "mall")
# Should have saved other fields
self.assertEqual(mall.town, town2)
self.assertEqual(mall.general_manager, gm2)
@mock.patch.object(ShoppingMallAdmin, "confirmation_fields", ["name"])
@mock.patch.object(ShoppingMallAdmin, "readonly_fields", ["shops"])
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
def test_readonly_fields_should_not_change(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
data = {
"id": mall.id,
"name": "name",
"general_manager": gm2.id,
"shops": [1],
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# Should not have cached the unsaved obj
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved changes yet
self.assertEqual(ShoppingMall.objects.count(), 1)
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_change"]
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/",
data=confirmation_received_data,
)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved obj
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
# should have updated fields that were in form
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.town, town2)
self.assertEqual(saved_item.general_manager, gm2)
# should have presevered the fields that are not in form (exclude)
for shop in saved_item.shops.all():
self.assertIn(shop, shops)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@@ -1,17 +1,20 @@
from unittest import mock
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 admin_confirm.tests.helpers import AdminConfirmTestCase
from tests.market.admin import ItemAdmin, InventoryAdmin, ShoppingMallAdmin
from tests.market.models import Item, Inventory, ShoppingMall
from tests.factories import ItemFactory, ShopFactory, InventoryFactory
class TestConfirmChangeAndAdd(ConfirmAdminTestCase):
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
class TestConfirmChangeAndAdd(AdminConfirmTestCase):
def test_get_add_without_confirm_add(self):
ItemAdmin.confirm_add = False
response = self.client.get(reverse("admin:market_item_add"))
self.assertFalse(response.context_data.get("confirm_add"))
self.assertNotIn("_confirm_add", response.rendered_content)
@@ -70,6 +73,36 @@ class TestConfirmChangeAndAdd(ConfirmAdminTestCase):
# Should not have been added yet
self.assertEqual(Inventory.objects.count(), 0)
def test_post_change_with_confirm_change_shoppingmall_name(self):
# When testing found that even though name was in confirmation_fields
# When only name changed, `form.is_valid` = False, and thus didn't trigger
# confirmation page previously, even though it should have
mall = ShoppingMall.objects.create(name="name")
data = {
"id": mall.id,
"name": "new name",
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
"_save": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data
)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/shoppingmall/change_confirmation.html",
"admin/market/change_confirmation.html",
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
self._assertSubmitHtml(rendered_content=response.rendered_content)
# Hasn't changed item yet
mall.refresh_from_db()
self.assertEqual(mall.name, "name")
def test_post_change_with_confirm_change(self):
item = ItemFactory(name="item")
data = {
@@ -100,7 +133,9 @@ class TestConfirmChangeAndAdd(ConfirmAdminTestCase):
self._assertSimpleFieldFormHtml(
rendered_content=response.rendered_content, fields=form_data
)
self._assertSubmitHtml(rendered_content=response.rendered_content)
self._assertSubmitHtml(
rendered_content=response.rendered_content, multipart_form=True
)
# Hasn't changed item yet
item.refresh_from_db()
@@ -120,9 +155,22 @@ class TestConfirmChangeAndAdd(ConfirmAdminTestCase):
def test_get_confirmation_fields_should_default_if_not_set(self):
expected_fields = [f.name for f in Item._meta.fields if f.name != "id"]
ItemAdmin.confirmation_fields = None
ItemAdmin.fields = expected_fields
admin = ItemAdmin(Item, AdminSite())
actual_fields = admin.get_confirmation_fields(self.factory.request())
self.assertEqual(expected_fields, actual_fields)
for field in expected_fields:
self.assertIn(field, actual_fields)
def test_get_confirmation_fields_default_should_only_include_fields_shown_on_admin(
self,
):
admin_fields = ["name", "price"]
ItemAdmin.confirmation_fields = None
ItemAdmin.fields = admin_fields
admin = ItemAdmin(Item, AdminSite())
actual_fields = admin.get_confirmation_fields(self.factory.request())
for field in admin_fields:
self.assertIn(field, actual_fields)
def test_get_confirmation_fields_if_set(self):
expected_fields = ["name", "currency"]
@@ -1,12 +1,15 @@
from unittest import mock
from admin_confirm.admin import AdminConfirmMixin
from django.urls import reverse
from admin_confirm.tests.helpers import ConfirmAdminTestCase
from admin_confirm.tests.helpers import AdminConfirmTestCase
from tests.market.admin import ShoppingMallAdmin
from tests.market.models import ShoppingMall
from tests.factories import ShopFactory
class TestConfirmChangeAndAddM2MField(ConfirmAdminTestCase):
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
class TestConfirmChangeAndAddM2MField(AdminConfirmTestCase):
def test_post_add_without_confirm_add_m2m(self):
shops = [ShopFactory() for i in range(3)]
@@ -84,6 +87,9 @@ class TestConfirmChangeAndAddM2MField(ConfirmAdminTestCase):
]
self.assertEqual(response.template_name, expected_templates)
# Should show two lists for the m2m current and modified values
self.assertEqual(response.rendered_content.count("<ul>"), 2)
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
@@ -0,0 +1,477 @@
from unittest import mock
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.cache import cache
from django.urls import reverse
from admin_confirm.tests.helpers import AdminConfirmTestCase
from tests.market.admin import ItemAdmin, ShoppingMallAdmin
from tests.market.models import GeneralManager, Item, ShoppingMall, Town
from tests.factories import ItemFactory, ShopFactory
from admin_confirm.constants import CACHE_KEYS, CONFIRMATION_RECEIVED
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
class TestConfirmSaveActions(AdminConfirmTestCase):
def test_simple_add_with_save(self):
# Load the Add Item Page
ItemAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_item_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Click "Save"
data = {
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_save",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
# Should not have saved the item yet
self.assertEqual(Item.objects.count(), 0)
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/market/item/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_simple_change_with_continue(self):
item = ItemFactory(name="Not name")
# Load the Change Item Page
ItemAdmin.confirm_change = True
response = self.client.get(f"/admin/market/item/{item.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Click "Save And Continue"
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_continue",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
# Should not have saved the changes yet
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{item.id}/change/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_file_and_image_add_addanother(self):
# Load the Add Item Page
ItemAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_item_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Select files
image_path = "screenshot.png"
f = SimpleUploadedFile(
name="test_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="test_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
# Click "Save"
data = {
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"file": f,
"image": i,
"_confirm_add": True,
"_addanother": True,
}
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_addanother",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
self.assertEqual(cached_item.file, data["file"])
self.assertEqual(cached_item.image, data["image"])
# Should not have saved the item yet
self.assertEqual(Item.objects.count(), 0)
# Click "Yes, I'm Sure"
confirmation_data = data.copy()
del confirmation_data["_confirm_add"]
del confirmation_data["image"]
del confirmation_data["file"]
confirmation_data[CONFIRMATION_RECEIVED] = True
response = self.client.post(
reverse("admin:market_item_add"), data=confirmation_data
)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
# Should show add page since "add another" was selected
self.assertEqual(response.url, "/admin/market/item/add/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
self.assertEqual(saved_item.file, data["file"])
self.assertEqual(saved_item.image, data["image"])
self.assertEqual(saved_item.file.name, "test_file.jpg")
self.assertEqual(saved_item.image.name, "test_image.jpg")
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_file_and_image_change_with_saveasnew(self):
item = ItemFactory(name="Not name")
# Select files
image_path = "screenshot.png"
f = SimpleUploadedFile(
name="test_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="test_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
item.file = f
item.image = i
item.save()
# Load the Change Item Page
ItemAdmin.confirm_change = True
ItemAdmin.fields = ["name", "price", "file", "image", "currency"]
ItemAdmin.save_as = True
ItemAdmin.save_as_continue = True
response = self.client.get(f"/admin/market/item/{item.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
# Click "Save And Continue"
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"image": i2,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_saveasnew",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
self.assertFalse(cached_item.file.name)
self.assertEqual(cached_item.image, i2)
# Should not have saved the changes yet
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertIsNotNone(item.file)
self.assertIsNotNone(item.image)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data["image"] = ""
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{item.id + 1}/change/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.file)
self.assertEqual(new_item.image, i2)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_relations_add(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
# Load the Add ShoppingMall Page
ShoppingMallAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_shoppingmall_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Click "Save"
data = {
"name": "name",
"shops": [s.id for s in shops],
"general_manager": gm.id,
"town": town.id,
"_confirm_add": True,
"_save": True,
}
response = self.client.post(reverse("admin:market_shoppingmall_add"), data=data)
# Should be shown confirmation page
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_save"
)
# Should not have cached the unsaved object
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_add"]
confirmation_received_data[CONFIRMATION_RECEIVED] = True
response = self.client.post(
reverse("admin:market_shoppingmall_add"), data=confirmation_received_data
)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/market/shoppingmall/")
# Should have saved object
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.general_manager, gm)
self.assertEqual(saved_item.town, town)
for shop in saved_item.shops.all():
self.assertIn(shop, shops)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_relation_change_with_saveasnew(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
# Load the Change ShoppingMall Page
ShoppingMallAdmin.confirm_change = True
response = self.client.get(f"/admin/market/shoppingmall/{mall.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Click "Save"
data = {
"id": mall.id,
"name": "name",
"shops": [s.id for s in shops2],
"general_manager": gm2.id,
"town": town2.id,
"_confirm_change": True,
"_saveasnew": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should be shown confirmation page
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_saveasnew"
)
# Should not have cached the unsaved obj
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved changes yet
self.assertEqual(ShoppingMall.objects.count(), 1)
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Click "Yes, I'm Sure"
confirmation_received_data = data.copy()
del confirmation_received_data["_confirm_change"]
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/",
data=confirmation_received_data,
)
# Should not have redirected to changelist
self.assertEqual(
response.url, f"/admin/market/shoppingmall/{mall.id + 1}/change/"
)
# Should have saved obj
self.assertEqual(ShoppingMall.objects.count(), 2)
# Should not have changed old obj
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Should have created new obj
saved_item = ShoppingMall.objects.filter(id=mall.id + 1).first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.general_manager, gm2)
self.assertEqual(saved_item.town, town2)
for shop in saved_item.shops.all():
self.assertIn(shop, shops2)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@@ -0,0 +1,458 @@
from unittest import mock
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.cache import cache
from django.urls import reverse
from admin_confirm.tests.helpers import AdminConfirmTestCase
from tests.market.admin import ItemAdmin, ShoppingMallAdmin
from tests.market.models import GeneralManager, Item, ShoppingMall, Town
from tests.factories import ItemFactory, ShopFactory
from admin_confirm.constants import CACHE_KEYS, CONFIRMATION_RECEIVED
@mock.patch.object(ShoppingMallAdmin, "inlines", [])
class TestConfirmationCache(AdminConfirmTestCase):
def test_simple_add(self):
# Load the Add Item Page
ItemAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_item_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Click "Save"
data = {
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_save",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
# Should not have saved the item yet
self.assertEqual(Item.objects.count(), 0)
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/market/item/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_simple_change(self):
item = ItemFactory(name="Not name")
# Load the Change Item Page
ItemAdmin.confirm_change = True
response = self.client.get(f"/admin/market/item/{item.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Click "Save And Continue"
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_continue",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
# Should not have saved the changes yet
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{item.id}/change/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_file_and_image_add(self):
# Load the Add Item Page
ItemAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_item_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Select files
image_path = "screenshot.png"
f = SimpleUploadedFile(
name="test_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="test_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
# Click "Save"
data = {
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0][0],
"file": f,
"image": i,
"_confirm_add": True,
"_save": True,
}
response = self.client.post(reverse("admin:market_item_add"), data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_save",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
self.assertEqual(cached_item.file, data["file"])
self.assertEqual(cached_item.image, data["image"])
# Should not have saved the item yet
self.assertEqual(Item.objects.count(), 0)
# Click "Yes, I'm Sure"
confirmation_data = data.copy()
del confirmation_data["_confirm_add"]
del confirmation_data["image"]
del confirmation_data["file"]
confirmation_data[CONFIRMATION_RECEIVED] = True
response = self.client.post(
reverse("admin:market_item_add"), data=confirmation_data
)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/market/item/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
self.assertEqual(saved_item.file, data["file"])
self.assertEqual(saved_item.image, data["image"])
self.assertEqual(saved_item.file.name, "test_file.jpg")
self.assertEqual(saved_item.image.name, "test_image.jpg")
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_file_and_image_change(self):
item = ItemFactory(name="Not name")
# Select files
image_path = "screenshot.png"
f = SimpleUploadedFile(
name="test_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="test_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
item.file = f
item.image = i
item.save()
# Load the Change Item Page
ItemAdmin.confirm_change = True
ItemAdmin.fields = ["name", "price", "file", "image", "currency"]
response = self.client.get(f"/admin/market/item/{item.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
# Click "Save And Continue"
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"image": i2,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_continue",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
self.assertFalse(cached_item.file.name)
self.assertEqual(cached_item.image, i2)
# Should not have saved the changes yet
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertIsNotNone(item.file)
self.assertIsNotNone(item.image)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data["image"] = ""
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{item.id}/change/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
self.assertFalse(saved_item.file)
self.assertEqual(saved_item.image, i2)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_relations_add(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
# Load the Add ShoppingMall Page
ShoppingMallAdmin.confirm_add = True
response = self.client.get(reverse("admin:market_shoppingmall_add"))
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
# Click "Save"
data = {
"name": "name",
"shops": [s.id for s in shops],
"general_manager": gm.id,
"town": town.id,
"_confirm_add": True,
"_save": True,
}
response = self.client.post(reverse("admin:market_shoppingmall_add"), data=data)
# Should be shown confirmation page
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_save"
)
# Should not have cached the unsaved object
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved the object yet
self.assertEqual(ShoppingMall.objects.count(), 0)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_add"]
response = self.client.post(
reverse("admin:market_shoppingmall_add"), data=confirmation_received_data
)
# Should have redirected to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/market/shoppingmall/")
# Should have saved object
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.general_manager, gm)
self.assertEqual(saved_item.town, town)
for shop in saved_item.shops.all():
self.assertIn(shop, shops)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_relation_change(self):
gm = GeneralManager.objects.create(name="gm")
shops = [ShopFactory() for i in range(3)]
town = Town.objects.create(name="town")
mall = ShoppingMall.objects.create(name="mall", general_manager=gm, town=town)
mall.shops.set(shops)
# new values
gm2 = GeneralManager.objects.create(name="gm2")
shops2 = [ShopFactory() for i in range(3)]
town2 = Town.objects.create(name="town2")
# Load the Change ShoppingMall Page
ShoppingMallAdmin.confirm_change = True
response = self.client.get(f"/admin/market/shoppingmall/{mall.id}/change/")
# Should be asked for confirmation
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
# Click "Save"
data = {
"id": mall.id,
"name": "name",
"shops": [s.id for s in shops2],
"general_manager": gm2.id,
"town": town2.id,
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/", data=data
)
# Should be shown confirmation page
self._assertManyToManyFormHtml(
rendered_content=response.rendered_content,
options=shops,
selected_ids=data["shops"],
)
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# Should not have cached the unsaved obj
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Should not have saved changes yet
self.assertEqual(ShoppingMall.objects.count(), 1)
mall.refresh_from_db()
self.assertEqual(mall.name, "mall")
self.assertEqual(mall.general_manager, gm)
self.assertEqual(mall.town, town)
for shop in mall.shops.all():
self.assertIn(shop, shops)
# Click "Yes, I'm Sure"
confirmation_received_data = data
del confirmation_received_data["_confirm_change"]
confirmation_received_data[CONFIRMATION_RECEIVED] = True
response = self.client.post(
f"/admin/market/shoppingmall/{mall.id}/change/",
data=confirmation_received_data,
)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/shoppingmall/{mall.id}/change/")
# Should have saved obj
self.assertEqual(ShoppingMall.objects.count(), 1)
saved_item = ShoppingMall.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.general_manager, gm2)
self.assertEqual(saved_item.town, town2)
for shop in saved_item.shops.all():
self.assertIn(shop, shops2)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
+169
View File
@@ -0,0 +1,169 @@
"""
Tests ModelAdmin with fieldsets custom configured through one of the possible methods
Ensures that AdminConfirmMixin works correctly when implimenting class alters default fieldsets
Test Matrix
method: `.fieldsets =`, `def get_fieldsets()`
action: change, add
fieldset: simple, with readonly fields, with custom fields
"""
import pytest
from importlib import reload
from tests.market.admin import item_admin
from django.contrib.auth.models import User
from django.contrib.admin import AdminSite
from django.test.client import RequestFactory
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.cache import cache
from admin_confirm.constants import CACHE_KEYS, CONFIRM_CHANGE, CONFIRMATION_RECEIVED
from tests.market.models import Item
from tests.factories import ItemFactory
def fs_simple(admin):
return (
(None, {"fields": ("name", "price", "image")}),
(
"Advanced options",
{
"classes": ("collapse",),
"fields": ("currency", "file"),
},
),
)
def fs_w_readonly(admin):
admin.readonly_fields = ["description", "image"]
return fs_simple(admin)
def fs_w_custom(admin):
admin.one = lambda self, obj: "ReadOnly"
admin.two = lambda self, obj: "ReadOnly"
admin.three = lambda self, obj: "ReadOnly"
admin.readonly_fields = ["one", "two", "three"]
return (
(None, {"fields": ("name", "price", "image", "one")}),
(
"Advanced options",
{
"classes": ("collapse",),
"fields": ("currency", "two", "file"),
},
),
("More Info", {"fields": ("three", "description")}),
)
def set_fieldsets(admin, fieldset):
admin.fieldsets = fieldset
def override_get_fieldsets(admin, fieldset):
admin.get_fieldsets = lambda self, request, obj=None: fieldset
methods = [set_fieldsets, override_get_fieldsets]
actions = ["_confirm_add", "_confirm_change"]
fieldsets = [fs_simple, fs_w_readonly, fs_w_custom]
param_matrix = []
for method in methods:
for fieldset in fieldsets:
for action in actions:
param_matrix.append((method, fieldset, action))
@pytest.mark.django_db()
@pytest.mark.parametrize("method,get_fieldset,action", param_matrix)
def test_fieldsets(client, method, get_fieldset, action):
reload(item_admin)
admin = item_admin.ItemAdmin
fs = get_fieldset(admin)
# set fieldsets via one of the methods
method(admin, fs)
admin_instance = admin(admin_site=AdminSite(), model=Item)
request = RequestFactory().request
assert admin_instance.get_fieldsets(request) == fs
user = User.objects.create_superuser(
username="super", email="super@email.org", password="pass"
)
client.force_login(user)
url = "/admin/market/item/add/"
image_path = "screenshot.png"
f2 = SimpleUploadedFile(
name="new_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i2 = SimpleUploadedFile(
name="new_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
data = {
"name": "new name",
"price": 2,
"currency": "USD",
"image": i2,
"file": f2,
action: True,
"_save": True,
}
for f in admin.readonly_fields:
if f in data.keys():
del data[f]
if action == CONFIRM_CHANGE:
url = "/admin/market/item/1/change/"
f = SimpleUploadedFile(
name="old_file.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="old_image.jpg",
content=open(image_path, "rb").read(),
content_type="image/jpeg",
)
item = ItemFactory(name="old name", price=1, currency="CAD", file=f, image=i)
data["id"] = item.id
cache_item = Item()
for f in ["name", "price", "currency", "image", "file"]:
if f not in admin.readonly_fields:
setattr(cache_item, f, data[f])
cache.set(CACHE_KEYS["object"], cache_item)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data[action]
data[CONFIRMATION_RECEIVED] = True
response = client.post(url, data=data)
# Should have redirected to changelist
# assert response.status_code == 302
assert response.url == "/admin/market/item/"
# Should have saved item
assert Item.objects.count() == 1
saved_item = Item.objects.all().first()
for f in ["name", "price", "currency"]:
if f not in admin.readonly_fields:
assert getattr(saved_item, f) == data[f]
if "file" not in admin.readonly_fields:
assert "new_file" in saved_item.file.name
if "image" not in admin.readonly_fields:
assert "new_image" in saved_item.image.name
reload(item_admin)
+967
View File
@@ -0,0 +1,967 @@
"""
Ensure that files are saved during confirmation
Without file changes, Django is relied on
With file changes, we cache the object, save it with
the files if new, or add files to existing obj and save
Then send the rest of the changes to Django to handle
This is arguably the most we fiddle with the Django request
Thus we should test it extensively
"""
import time
from unittest import mock
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.cache import cache
from admin_confirm.tests.helpers import AdminConfirmTestCase
from admin_confirm.constants import CACHE_KEYS, CONFIRMATION_RECEIVED
from tests.market.admin import ItemAdmin
from tests.market.models import Item, Shop
from tests.factories import ItemFactory, ShopFactory
class TestFileCache(AdminConfirmTestCase):
def setUp(self):
# Load the Change Item Page
ItemAdmin.confirm_change = True
ItemAdmin.fields = ["name", "price", "file", "image", "currency"]
ItemAdmin.save_as = True
ItemAdmin.save_as_continue = True
self.image_path = "screenshot.png"
f = SimpleUploadedFile(
name="test_file.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
i = SimpleUploadedFile(
name="test_image.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
self.item = ItemFactory(name="Not name", file=f, image=i)
return super().setUp()
def test_save_as_continue_true_should_not_redirect_to_changelist(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = True
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
cache.set(CACHE_KEYS["object"], cache_item)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{self.item.id + 1}/change/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.file)
self.assertEqual(new_item.image.name.count("test_image2"), 1)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_save_as_continue_false_should_redirect_to_changelist(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
cache.set(CACHE_KEYS["object"], cache_item)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.file)
self.assertEqual(new_item.image.name.count("test_image2"), 1)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_saveasnew_without_any_file_changes_should_save_new_instance_without_files(
self,
):
item = self.item
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"image": "",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
)
cache.set(CACHE_KEYS["object"], cache_item)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{self.item.id + 1}/change/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
# In Django (by default), the save as new does not transfer over the files
self.assertFalse(new_item.file)
self.assertFalse(new_item.image)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_add_with_upload_file_should_save_new_instance_with_files(self):
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
cache.set(CACHE_KEYS["object"], cache_item)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
self.item.refresh_from_db()
self.assertEqual(self.item.name, "Not name")
self.assertEqual(self.item.file.name.count("test_file"), 1)
self.assertEqual(self.item.image.name.count("test_image2"), 0)
self.assertEqual(self.item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=self.item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertIn("test_file2", new_item.file.name)
self.assertFalse(new_item.image)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_add_without_cached_post_should_save_new_instance_with_file(self):
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
cache.set(CACHE_KEYS["object"], cache_item)
# Make sure there's no post cached post
cache.delete(CACHE_KEYS["post"])
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
self.item.refresh_from_db()
self.assertEqual(self.item.name, "Not name")
self.assertEqual(self.item.file.name.count("test_file"), 1)
self.assertEqual(self.item.image.name.count("test_image2"), 0)
self.assertEqual(self.item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=self.item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.image)
# Able to save the cached file since cached object was there even though cached post was not
self.assertIn("test_file2", new_item.file.name)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_add_without_cached_object_should_save_new_instance_but_not_have_file(self):
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
# Make sure there's no post cached obj
cache.delete(CACHE_KEYS["object"])
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
self.item.refresh_from_db()
self.assertEqual(self.item.name, "Not name")
self.assertEqual(self.item.file.name.count("test_file"), 1)
self.assertEqual(self.item.image.name.count("test_image2"), 0)
self.assertEqual(self.item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=self.item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.image)
# FAILED to save the file, because cached item was not there
self.assertFalse(new_item.file)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_add_without_any_cache_should_save_new_instance_but_not_have_file(self):
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Set cache
cache_item = Item(
name=data["name"], price=data["price"], currency=data["currency"], file=f2
)
# Make sure there's no cache
cache.delete(CACHE_KEYS["object"])
cache.delete(CACHE_KEYS["post"])
# Click "Yes, I'm Sure"
del data["_confirm_add"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(f"/admin/market/item/add/", data=data)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
self.item.refresh_from_db()
self.assertEqual(self.item.name, "Not name")
self.assertEqual(self.item.file.name.count("test_file"), 1)
self.assertEqual(self.item.image.name.count("test_image2"), 0)
self.assertEqual(self.item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=self.item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.image)
# FAILED to save the file, because cached item was not there
self.assertFalse(new_item.file)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_change_without_cached_post_should_save_file_changes(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"image": i2,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
cache.set(CACHE_KEYS["object"], cache_item)
# Ensure no cached post
cache.delete(CACHE_KEYS["post"])
# Click "Yes, I'm Sure"
del data["_confirm_change"]
# Image would have been in FILES and not in POST
del data["image"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
# Should have cleared `file` since clear was selected
self.assertFalse(new_item.file)
# Saved cached file from cached obj even if cached post was missing
self.assertIn("test_image2", new_item.image.name)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_change_without_cached_object_should_save_but_without_file_changes(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
# Ensure no cached obj
cache.delete(CACHE_KEYS["object"])
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.file)
# FAILED to save image
self.assertFalse(new_item.image)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_change_without_any_cache_should_save_but_not_have_file_changes(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_saveasnew": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
image=i2,
)
# Ensure no cache
cache.delete(CACHE_KEYS["object"])
cache.delete(CACHE_KEYS["post"])
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/2/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should not have changed existing item
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertEqual(item.file.name.count("test_file"), 1)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have saved new item
self.assertEqual(Item.objects.count(), 2)
new_item = Item.objects.filter(id=item.id + 1).first()
self.assertIsNotNone(new_item)
self.assertEqual(new_item.name, data["name"])
self.assertEqual(new_item.price, data["price"])
self.assertEqual(new_item.currency, data["currency"])
self.assertFalse(new_item.file)
# FAILED to save image
self.assertFalse(new_item.image)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_change_without_changing_file_should_save_changes(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"image": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_save": True,
}
# Set cache
cache_item = Item(
name=data["name"],
price=data["price"],
currency=data["currency"],
)
cache.get(CACHE_KEYS["object"], cache_item)
cache.get(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/1/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should have changed existing item
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "name")
# Should have cleared if requested
self.assertFalse(item.file.name)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@mock.patch("admin_confirm.admin.CACHE_TIMEOUT", 1)
def test_old_cache_should_not_be_used(self):
item = self.item
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Click "Save And Continue"
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"image": i2,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content,
save_action="_continue",
multipart_form=True,
)
# Should have cached the unsaved item
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNotNone(cached_item)
self.assertIsNone(cached_item.id)
self.assertEqual(cached_item.name, data["name"])
self.assertEqual(cached_item.price, data["price"])
self.assertEqual(cached_item.currency, data["currency"])
self.assertFalse(cached_item.file.name)
self.assertEqual(cached_item.image, i2)
# Should not have saved the changes yet
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "Not name")
self.assertIsNotNone(item.file)
self.assertIsNotNone(item.image)
# Wait for cache to time out
time.sleep(1)
# Check that it did time out
cached_item = cache.get(CACHE_KEYS["object"])
self.assertIsNone(cached_item)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data["image"] = ""
data[CONFIRMATION_RECEIVED] = True
response = self.client.post(f"/admin/market/item/{item.id}/change/", data=data)
# Should not have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/{item.id}/change/")
# Should have saved item
self.assertEqual(Item.objects.count(), 1)
saved_item = Item.objects.all().first()
self.assertEqual(saved_item.name, data["name"])
self.assertEqual(saved_item.price, data["price"])
self.assertEqual(saved_item.currency, data["currency"])
self.assertFalse(saved_item.file)
# SHOULD not have saved image since it was in the old cache
self.assertNotIn("test_image2", saved_item.image)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_cache_with_incorrect_model_should_not_be_used(self):
item = self.item
# Load the Change Item Page
ItemAdmin.save_as_continue = False
# Upload new image and remove file
i2 = SimpleUploadedFile(
name="test_image2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"id": item.id,
"name": "name",
"price": 2.0,
"file": "",
"file-clear": "on",
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_change": True,
"_save": True,
}
# Set cache to incorrect model
cache_obj = Shop(name="ShopName")
cache.set(CACHE_KEYS["object"], cache_obj)
cache.set(CACHE_KEYS["post"], data)
# Click "Yes, I'm Sure"
del data["_confirm_change"]
data[CONFIRMATION_RECEIVED] = True
with mock.patch.object(ItemAdmin, "message_user") as message_user:
response = self.client.post(
f"/admin/market/item/{self.item.id}/change/", data=data
)
# Should show message to user with correct obj and path
message_user.assert_called_once()
message = message_user.call_args[0][1]
self.assertIn("/admin/market/item/1/change/", message)
self.assertIn(data["name"], message)
self.assertNotIn("You may edit it again below.", message)
# Should have redirected to changelist
self.assertEqual(response.url, f"/admin/market/item/")
# Should have changed existing item
self.assertEqual(Item.objects.count(), 1)
item.refresh_from_db()
self.assertEqual(item.name, "name")
# Should have cleared if requested
self.assertFalse(item.file.name)
self.assertEqual(item.image.name.count("test_image2"), 0)
self.assertEqual(item.image.name.count("test_image"), 1)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
def test_form_without_files_should_not_use_cache(self):
cache.delete_many(CACHE_KEYS.values())
shop = ShopFactory()
# Click "Save And Continue"
data = {
"id": shop.id,
"name": "name",
"_confirm_change": True,
"_continue": True,
}
response = self.client.post(f"/admin/market/shop/{shop.id}/change/", data=data)
# Should be shown confirmation page
self._assertSubmitHtml(
rendered_content=response.rendered_content, save_action="_continue"
)
# Should not have set cache since not multipart form
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))