fix(Issue-27): Confirmations not triggered on models with validation failures (#30)

* feat(ISSUE-27): Added some validation integration tests

* feat(ISSUE-27): Integration tests for validations

* feat(ISSUE-27): Unable to select autocomplete with selenium

* feat(ISSUE-27): Fix file caching with proper FileCache

* feat(ISSUE-27): Some minor tweaks

* feat(ISSUE-27): Try reviewdog

* feat(ISSUE-27): Remove noisy linting

* feat(ISSUE-27): Black format

* feat(ISSUE-27): Adding some simple file cache tests

* feat(ISSUE-27): Some cleaning up

Co-authored-by: Thu Trang Pham <thu@joinmodernhealth.com>
This commit is contained in:
Thu Trang Pham
2021-04-28 12:03:16 -07:00
committed by GitHub
parent 4d6b2900d8
commit 14dc6268b0
23 changed files with 1593 additions and 1108 deletions
@@ -102,10 +102,6 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
# 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)
@@ -171,16 +167,6 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
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)
@@ -205,13 +191,14 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
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.assertIsNotNone(saved_item.file)
self.assertIsNotNone(saved_item.image)
self.assertEqual(saved_item.file.name, "test_file.jpg")
self.assertEqual(saved_item.image.name, "test_image.jpg")
self.assertRegex(saved_item.file.name, r"test_file.*\.jpg$")
self.assertRegex(saved_item.image.name, r"test_image.*\.jpg$")
# Should have cleared cache
self.assertEqual(len(ItemAdmin._file_cache.cached_keys), 0)
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@@ -271,16 +258,6 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
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()
@@ -312,11 +289,11 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
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)
self.assertIsNotNone(new_item.image)
self.assertRegex(new_item.image.name, r"test_image2.*\.jpg$")
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
self.assertEqual(len(ItemAdmin._file_cache.cached_keys), 0)
def test_relations_add(self):
gm = GeneralManager.objects.create(name="gm")
@@ -379,6 +356,7 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
self.assertIn(shop, shops)
# Should have cleared cache
self.assertEqual(len(ItemAdmin._file_cache.cached_keys), 0)
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
@@ -473,5 +451,4 @@ class TestConfirmSaveActions(AdminConfirmTestCase):
self.assertIn(shop, shops2)
# Should have cleared cache
for key in CACHE_KEYS.values():
self.assertIsNone(cache.get(key))
self.assertEqual(len(ItemAdmin._file_cache.cached_keys), 0)
@@ -102,10 +102,6 @@ class TestConfirmationCache(AdminConfirmTestCase):
# 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)
@@ -204,11 +200,11 @@ class TestConfirmationCache(AdminConfirmTestCase):
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.assertIsNotNone(saved_item.file)
self.assertIsNotNone(saved_item.image)
self.assertEqual(saved_item.file.name, "test_file.jpg")
self.assertEqual(saved_item.image.name, "test_image.jpg")
self.assertRegex(saved_item.file.name, r"test_file.*\.jpg$")
self.assertRegex(saved_item.image.name, r"test_image.*\.jpg$")
# Should have cleared cache
for key in CACHE_KEYS.values():
@@ -271,12 +267,6 @@ class TestConfirmationCache(AdminConfirmTestCase):
# 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)
@@ -301,7 +291,9 @@ class TestConfirmationCache(AdminConfirmTestCase):
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)
self.assertIsNotNone(saved_item.image)
self.assertRegex(saved_item.image.name, r"test_image2.*\.jpg$")
# Should have cleared cache
for key in CACHE_KEYS.values():
@@ -0,0 +1,920 @@
"""
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
"""
from admin_confirm.utils import format_cache_key
from admin_confirm.file_cache import FileCache
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 TestConfirmationUsingFileCache(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,
)
file_cache = FileCache()
file_cache.set(format_cache_key(model="Item", field="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,
)
file_cache = FileCache()
file_cache.set(format_cache_key(model="Item", field="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, "/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):
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"file": f2,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# 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("/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, "/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.assertIsNotNone(new_item.file)
self.assertFalse(new_item.image)
self.assertRegex(new_item.file.name, r"test_file2.*\.jpg$")
# 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):
# Upload new file
f2 = SimpleUploadedFile(
name="test_file2.jpg",
content=open(self.image_path, "rb").read(),
content_type="image/jpeg",
)
# Request.POST
data = {
"name": "name",
"price": 2.0,
"image": "",
"file": f2,
"currency": Item.VALID_CURRENCIES[0][0],
"_confirm_add": True,
"_save": True,
}
# 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("/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, "/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.assertRegex(new_item.file.name, r"test_file2.*\.jpg$")
# 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,
}
# 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("/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, "/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,
}
# 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("/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, "/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,
)
file_cache = FileCache()
file_cache.set(format_cache_key(model="Item", field="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, "/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)
self.assertIsNotNone(new_item.image)
# 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
# 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,
}
# 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, "/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
# 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,
}
# 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, "/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, "/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)
# 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
# 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, "/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))
+32 -910
View File
@@ -1,911 +1,33 @@
"""
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, "/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("/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, "/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("/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, "/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,
}
# 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("/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, "/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,
}
# 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("/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, "/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, "/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
# 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,
}
# 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, "/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
# 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,
}
# 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, "/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, "/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
# 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, "/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))
from admin_confirm.file_cache import FileCache
file = SimpleUploadedFile(
name="test_file.jpg",
content=open("screenshot.png", "rb").read(),
content_type="image/jpeg",
)
def test_should_set_file_cache():
file_cache = FileCache()
file_cache.set("key", file)
assert "key" in file_cache.cached_keys
assert file_cache.get("key") is not None
def test_should_delete_file_cache():
file_cache = FileCache()
file_cache.set("key", file)
file_cache.delete("key")
assert "key" not in file_cache.cached_keys
assert file_cache.get("key") is None
def test_should_delete_all_file_cache():
file_cache = FileCache()
file_cache.set("key", file)
file_cache.set("key2", file)
file_cache.delete_all()
assert len(file_cache.cached_keys) == 0
assert file_cache.get("key") is None
assert file_cache.get("key2") is None
@@ -1,7 +1,11 @@
"""
Ensures that confirmations work with validators on the Model and on the Modelform.
NOTE: These unit tests are not enough to ensure that confirmations work with validators.
Please ensure to add integration tests too!
"""
from admin_confirm.constants import CONFIRM_ADD
from unittest import mock
from django.urls import reverse
from django.utils import timezone
@@ -80,27 +84,8 @@ class TestWithValidators(AdminConfirmTestCase):
"_save": True,
}
response = self.client.post(reverse("admin:market_itemsale_add"), data)
# Should not have been added yet
self.assertEqual(ItemSale.objects.count(), 0)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/itemsale/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)
# Confirmation page would not have the _confirm_add sent on submit
del data["_confirm_add"]
# Selecting to "Yes, I'm sure" on the confirmation page
# Would post to the same endpoint
response = self.client.post(reverse("admin:market_itemsale_add"), data)
# Should not have redirected, since there was an error
# Should show form with error and not confirmation page
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/itemsale/change_form.html",
@@ -109,8 +94,10 @@ class TestWithValidators(AdminConfirmTestCase):
]
self.assertEqual(response.template_name, expected_templates)
self.assertEqual(ItemSale.objects.count(), 0)
self.assertTrue("error" in str(response.content))
self.assertTrue("Invalid Currency" in str(response.content))
self.assertIn("error", str(response.rendered_content))
self.assertIn("Invalid Currency", str(response.rendered_content))
# Should still ask for confirmation
self.assertIn(CONFIRM_ADD, response.rendered_content)
def test_can_confirm_for_models_with_clean_overridden(self):
shop = ShopFactory()
@@ -162,6 +149,7 @@ class TestWithValidators(AdminConfirmTestCase):
item = ItemFactory()
InventoryFactory(shop=shop, item=item, quantity=1)
transaction = TransactionFactory(shop=shop)
# Asking to buy more than the shop has in stock
data = {
"transaction": transaction.id,
"item": item.id,
@@ -176,6 +164,28 @@ class TestWithValidators(AdminConfirmTestCase):
# Should not have been added yet
self.assertEqual(ItemSale.objects.count(), 0)
# Ensure it shows the form and not the confirmation page
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/itemsale/change_form.html",
"admin/market/change_form.html",
"admin/change_form.html",
]
self.assertEqual(response.template_name, expected_templates)
self.assertTrue("error" in str(response.content))
self.assertTrue(
"Shop does not have enough of the item stocked" in str(response.content)
)
# Should still be asking for confirmation
self.assertIn(CONFIRM_ADD, response.rendered_content)
# Fix the issue by buying only what shop has in stock
data["quantity"] = 1
# _confirm_add would still be in the POST data
response = self.client.post(reverse("admin:market_itemsale_add"), data)
# Should show confirmation page
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
expected_templates = [
@@ -187,26 +197,6 @@ class TestWithValidators(AdminConfirmTestCase):
self._assertSubmitHtml(rendered_content=response.rendered_content)
# Confirmation page would not have the _confirm_add sent on submit
del data["_confirm_add"]
# Selecting to "Yes, I'm sure" on the confirmation page
# Would post to the same endpoint
response = self.client.post(reverse("admin:market_itemsale_add"), data)
# Should not have redirected, since there was an error
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/itemsale/change_form.html",
"admin/market/change_form.html",
"admin/change_form.html",
]
self.assertEqual(response.template_name, expected_templates)
self.assertEqual(ItemSale.objects.count(), 0)
self.assertTrue("error" in str(response.content))
self.assertTrue(
"Shop does not have enough of the item stocked" in str(response.content)
)
def test_can_confirm_for_modelform_with_clean_field_and_clean_overridden(self):
shop = ShopFactory()
data = {
@@ -270,27 +260,19 @@ class TestWithValidators(AdminConfirmTestCase):
# Should not have been added yet
self.assertEqual(Checkout.objects.count(), 0)
# Ensure not redirected (confirmation page does not redirect)
# Should show form with error and not confirmation page
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/checkout/change_confirmation.html",
"admin/market/change_confirmation.html",
"admin/change_confirmation.html",
"admin/market/checkout/change_form.html",
"admin/market/change_form.html",
"admin/change_form.html",
]
self.assertEqual(response.template_name, expected_templates)
self._assertSubmitHtml(rendered_content=response.rendered_content)
# Confirmation page would not have the _confirm_add sent on submit
del data["_confirm_add"]
# Selecting to "Yes, I'm sure" on the confirmation page
# Would post to the same endpoint
response = self.client.post(reverse("admin:market_checkout_add"), data)
print(response.content)
self.assertEqual(response.status_code, 200)
self.assertEqual(Checkout.objects.count(), 0)
self.assertIn("error", str(response.content))
self.assertIn("Invalid Total 111", str(response.content))
self.assertIn("error", str(response.rendered_content))
self.assertIn("Invalid Total 111", str(response.rendered_content))
# Should still ask for confirmation
self.assertIn(CONFIRM_ADD, response.rendered_content)
def test_cannot_confirm_for_modelform_with_clean_overridden_if_validation_fails(
self,
@@ -311,24 +293,19 @@ class TestWithValidators(AdminConfirmTestCase):
# Should not have been added yet
self.assertEqual(Checkout.objects.count(), 0)
# Ensure not redirected (confirmation page does not redirect)
# Should not have been added yet
self.assertEqual(Checkout.objects.count(), 0)
# Should show form with error and not confirmation page
self.assertEqual(response.status_code, 200)
expected_templates = [
"admin/market/checkout/change_confirmation.html",
"admin/market/change_confirmation.html",
"admin/change_confirmation.html",
"admin/market/checkout/change_form.html",
"admin/market/change_form.html",
"admin/change_form.html",
]
self.assertEqual(response.template_name, expected_templates)
self._assertSubmitHtml(rendered_content=response.rendered_content)
# Confirmation page would not have the _confirm_add sent on submit
del data["_confirm_add"]
# Selecting to "Yes, I'm sure" on the confirmation page
# Would post to the same endpoint
response = self.client.post(reverse("admin:market_checkout_add"), data)
print(response.content)
self.assertEqual(response.status_code, 200)
self.assertEqual(Checkout.objects.count(), 0)
self.assertIn("error", str(response.content))
self.assertIn("Invalid Total 222", str(response.content))
self.assertIn("error", str(response.rendered_content))
self.assertIn("Invalid Total 222", str(response.rendered_content))
# Should still ask for confirmation
self.assertIn(CONFIRM_ADD, response.rendered_content)