Added testing for DateField, DateTimeField, and validators on the Model and ModelForm (#23)
* Added testing for DateField and DateTimeField * Update makefile * Adding some of the tests with validators on model * Added ModelForm clean_field and clean tests Co-authored-by: Thu Trang Pham <thu@joinmodernhealth.com>main
parent
c88eb16a9b
commit
4d6b2900d8
7
Makefile
7
Makefile
|
|
@ -22,14 +22,17 @@ migrate:
|
|||
shell:
|
||||
./tests/manage.py shell
|
||||
|
||||
dbshell:
|
||||
./tests/manage.py dbshell
|
||||
|
||||
package:
|
||||
python3 setup.py sdist bdist_wheel
|
||||
|
||||
upload-testpypi:
|
||||
python3 -m twine upload --repository testpypi dist/django_admin_confirm-$(VERSION)*
|
||||
python3 -m twine upload --repository testpypi dist/django_admin_confirm-$(VERSION)-*
|
||||
|
||||
i-have-tested-with-testpypi-and-am-ready-to-release:
|
||||
python3 -m twine upload --repository pypi dist/django_admin_confirm-$(VERSION)*
|
||||
python3 -m twine upload --repository pypi dist/django_admin_confirm-$(VERSION)-*
|
||||
|
||||
install-testpypi:
|
||||
pip uninstall django_admin_confirm
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ These are some areas which might/probably have issues that are not currently tes
|
|||
- [x] ManyToManyField
|
||||
- [x] OneToOneField
|
||||
- [x] ForeignKey
|
||||
|
||||
- [x] DateField
|
||||
- [x] DateTimeField
|
||||
- [x] Custom Readonly fields
|
||||
|
||||
### Options
|
||||
|
|
@ -104,3 +105,22 @@ Note: Currently the code always calls super().\_changeform_view(), which would e
|
|||
|
||||
- [x] ModelAdmin.has_add_permission
|
||||
- [x] ModelAdmin.has_change_permission
|
||||
|
||||
### Tests for confirming models/forms with validations
|
||||
|
||||
- [x] ModelForm.clean_field
|
||||
- [x] ModelForm.clean
|
||||
- [x] Model.clean
|
||||
- [x] validator on the model field
|
||||
|
||||
There are other possible combos of theses
|
||||
|
||||
### Tests where save functions are overridden
|
||||
|
||||
- [ ] ModelForm.save
|
||||
- [ ] ModelAdmin.save_model
|
||||
|
||||
### Tests for storage backends for ImageField and FileField
|
||||
|
||||
- [x] Local storage
|
||||
- [ ] S3
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""
|
||||
Tests with different form input types
|
||||
"""
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
from importlib import reload
|
||||
from tests.factories import ShopFactory
|
||||
from tests.factories import ShopFactory, TransactionFactory
|
||||
from tests.market.models import GeneralManager, Item, ShoppingMall, Town
|
||||
|
||||
from admin_confirm.tests.helpers import AdminConfirmIntegrationTestCase
|
||||
|
|
@ -98,3 +100,37 @@ class ConfirmWithFormInputTypes(AdminConfirmIntegrationTestCase):
|
|||
mall.refresh_from_db()
|
||||
self.assertIn("New Name", mall.name)
|
||||
self.assertEqual(gm2, mall.general_manager)
|
||||
|
||||
def test_datetime_and_field_should_work(self):
|
||||
original_timestamp = timezone.now() - timedelta(hours=1)
|
||||
transaction = TransactionFactory(timestamp=original_timestamp)
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url + f"/admin/market/transaction/{transaction.id}/change/"
|
||||
)
|
||||
self.assertIn(CONFIRM_CHANGE, self.selenium.page_source)
|
||||
|
||||
# Set date via text input
|
||||
date_input = self.selenium.find_element(By.ID, "id_date")
|
||||
date_input.clear()
|
||||
date_input.send_keys("2021-01-01")
|
||||
self.assertEqual(date_input.get_attribute("value"), "2021-01-01")
|
||||
|
||||
# Set timestamp via text input
|
||||
timestamp_date = self.selenium.find_element(By.ID, "id_timestamp_0")
|
||||
timestamp_date.clear()
|
||||
timestamp_date.send_keys(str(timezone.now().date()))
|
||||
timestamp_time = self.selenium.find_element(By.ID, "id_timestamp_1")
|
||||
timestamp_time.clear()
|
||||
timestamp_time.send_keys(str(timezone.now().time()))
|
||||
|
||||
# Click save and continue
|
||||
self.selenium.find_element(By.NAME, "_continue").click()
|
||||
|
||||
# Click Yes I'm Sure on confirmation page
|
||||
self.assertIn("Confirm", self.selenium.page_source)
|
||||
self.selenium.find_element(By.NAME, "_continue").click()
|
||||
|
||||
transaction.refresh_from_db()
|
||||
self.assertEqual(str(transaction.date), "2021-01-01")
|
||||
self.assertTrue(transaction.timestamp > original_timestamp)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from admin_confirm.tests.helpers import AdminConfirmTestCase
|
||||
from tests.market.models import Transaction
|
||||
from tests.factories import ShopFactory, TransactionFactory
|
||||
|
||||
|
||||
class TestModelFieldTypes(AdminConfirmTestCase):
|
||||
def test_confirm_add_of_datetime_and_field(self):
|
||||
shop = ShopFactory()
|
||||
expected_date = timezone.now().date()
|
||||
expected_timestamp = timezone.now()
|
||||
data = {
|
||||
"date": str(expected_date),
|
||||
"timestamp_0": str(expected_timestamp.date()),
|
||||
"timestamp_1": str(expected_timestamp.time()),
|
||||
"currency": "USD",
|
||||
"shop": shop.id,
|
||||
"total": 0,
|
||||
"_confirm_add": True,
|
||||
"_save": True,
|
||||
}
|
||||
response = self.client.post(reverse("admin:market_transaction_add"), data)
|
||||
|
||||
# Should not have been added yet
|
||||
self.assertEqual(Transaction.objects.count(), 0)
|
||||
|
||||
# Ensure not redirected (confirmation page does not redirect)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
expected_templates = [
|
||||
"admin/market/transaction/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_transaction_add"), data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/admin/market/transaction/")
|
||||
self.assertEqual(Transaction.objects.count(), 1)
|
||||
|
||||
# Ensure that the date and timestamp saved correctly
|
||||
transaction = Transaction.objects.first()
|
||||
self.assertEqual(transaction.date, expected_date)
|
||||
self.assertEqual(transaction.timestamp, expected_timestamp)
|
||||
|
||||
def test_confirm_change_of_datetime_and_date_field(self):
|
||||
transaction = TransactionFactory()
|
||||
original_date = transaction.date
|
||||
original_timestamp = transaction.timestamp
|
||||
data = {
|
||||
"id": transaction.id,
|
||||
"date": "2021-01-01",
|
||||
"timestamp_0": "2021-01-01",
|
||||
"timestamp_1": "12:30:00",
|
||||
"currency": "USD",
|
||||
"shop": transaction.shop.id,
|
||||
"total": 0,
|
||||
"_confirm_change": True,
|
||||
"csrfmiddlewaretoken": "fake token",
|
||||
"_continue": True,
|
||||
}
|
||||
response = self.client.post(
|
||||
f"/admin/market/transaction/{transaction.id}/change/", data
|
||||
)
|
||||
# Ensure not redirected (confirmation page does not redirect)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
expected_templates = [
|
||||
"admin/market/transaction/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, save_action="_continue"
|
||||
)
|
||||
|
||||
# Hasn't changed item yet
|
||||
transaction.refresh_from_db()
|
||||
self.assertEqual(transaction.date, original_date)
|
||||
self.assertEqual(transaction.timestamp, original_timestamp)
|
||||
|
||||
# Selecting to "Yes, I'm sure" on the confirmation page
|
||||
# Would post to the same endpoint
|
||||
del data["_confirm_change"]
|
||||
response = self.client.post(
|
||||
f"/admin/market/transaction/{transaction.id}/change/", data
|
||||
)
|
||||
# will show the change page for this transaction
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(
|
||||
response.url, f"/admin/market/transaction/{transaction.id}/change/"
|
||||
)
|
||||
# Should not be the confirmation page, we already confirmed change
|
||||
self.assertNotEqual(response.templates, expected_templates)
|
||||
self.assertEqual(Transaction.objects.count(), 1)
|
||||
|
||||
transaction.refresh_from_db()
|
||||
self.assertEqual(str(transaction.date), "2021-01-01")
|
||||
self.assertEqual(str(transaction.timestamp.date()), "2021-01-01")
|
||||
self.assertEqual(str(transaction.timestamp.time()), "12:30:00")
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
"""
|
||||
Ensures that confirmations work with validators on the Model and on the Modelform.
|
||||
"""
|
||||
|
||||
from unittest import mock
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from admin_confirm.tests.helpers import AdminConfirmTestCase
|
||||
from tests.market.models import Checkout, ItemSale
|
||||
from tests.factories import (
|
||||
InventoryFactory,
|
||||
ItemFactory,
|
||||
ShopFactory,
|
||||
TransactionFactory,
|
||||
)
|
||||
|
||||
|
||||
class TestWithValidators(AdminConfirmTestCase):
|
||||
@mock.patch("tests.market.models.ItemSale.clean")
|
||||
def test_can_confirm_for_models_with_validator_on_model_field(self, _mock_clean):
|
||||
# ItemSale.currency has a validator on it
|
||||
item = ItemFactory()
|
||||
transaction = TransactionFactory()
|
||||
data = {
|
||||
"transaction": transaction.id,
|
||||
"item": item.id,
|
||||
"quantity": 1,
|
||||
"currency": "USD",
|
||||
"total": 10.00,
|
||||
"_confirm_add": True,
|
||||
"_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)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/admin/market/itemsale/")
|
||||
self.assertEqual(ItemSale.objects.count(), 1)
|
||||
|
||||
# Ensure that the date and timestamp saved correctly
|
||||
item_sale = ItemSale.objects.first()
|
||||
self.assertEqual(item_sale.transaction, transaction)
|
||||
self.assertEqual(item_sale.item, item)
|
||||
self.assertEqual(item_sale.currency, "USD")
|
||||
|
||||
def test_cannot_confirm_for_models_with_validator_on_model_field_if_validator_fails(
|
||||
self,
|
||||
):
|
||||
# ItemSale.currency has a validator on it
|
||||
shop = ShopFactory()
|
||||
item = ItemFactory()
|
||||
InventoryFactory(shop=shop, item=item, quantity=10)
|
||||
transaction = TransactionFactory(shop=shop)
|
||||
data = {
|
||||
"transaction": transaction.id,
|
||||
"item": item.id,
|
||||
"quantity": 1,
|
||||
"currency": "FAKE",
|
||||
"total": 10.00,
|
||||
"_confirm_add": True,
|
||||
"_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
|
||||
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("Invalid Currency" in str(response.content))
|
||||
|
||||
def test_can_confirm_for_models_with_clean_overridden(self):
|
||||
shop = ShopFactory()
|
||||
item = ItemFactory()
|
||||
InventoryFactory(shop=shop, item=item, quantity=10)
|
||||
transaction = TransactionFactory(shop=shop)
|
||||
data = {
|
||||
"transaction": transaction.id,
|
||||
"item": item.id,
|
||||
"quantity": 9,
|
||||
"currency": "USD",
|
||||
"total": 10.00,
|
||||
"_confirm_add": True,
|
||||
"_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)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/admin/market/itemsale/")
|
||||
self.assertEqual(ItemSale.objects.count(), 1)
|
||||
|
||||
# Ensure that the date and timestamp saved correctly
|
||||
item_sale = ItemSale.objects.first()
|
||||
self.assertEqual(item_sale.transaction, transaction)
|
||||
self.assertEqual(item_sale.item, item)
|
||||
self.assertEqual(item_sale.currency, "USD")
|
||||
|
||||
def test_cannot_confirm_for_models_with_clean_overridden_if_clean_fails(self):
|
||||
shop = ShopFactory()
|
||||
item = ItemFactory()
|
||||
InventoryFactory(shop=shop, item=item, quantity=1)
|
||||
transaction = TransactionFactory(shop=shop)
|
||||
data = {
|
||||
"transaction": transaction.id,
|
||||
"item": item.id,
|
||||
"quantity": 9,
|
||||
"currency": "USD",
|
||||
"total": 10.00,
|
||||
"_confirm_add": True,
|
||||
"_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
|
||||
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 = {
|
||||
"shop": shop.id,
|
||||
"currency": "USD",
|
||||
"total": 10.00,
|
||||
"date": str(timezone.now().date()),
|
||||
"timestamp_0": str(timezone.now().date()),
|
||||
"timestamp_1": str(timezone.now().time()),
|
||||
"_confirm_add": True,
|
||||
"_save": True,
|
||||
}
|
||||
response = self.client.post(reverse("admin:market_checkout_add"), data)
|
||||
|
||||
# Should not have been added yet
|
||||
self.assertEqual(Checkout.objects.count(), 0)
|
||||
|
||||
# Ensure not redirected (confirmation page does not redirect)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
expected_templates = [
|
||||
"admin/market/checkout/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_checkout_add"), data)
|
||||
print(response.content)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/admin/market/checkout/")
|
||||
self.assertEqual(Checkout.objects.count(), 1)
|
||||
|
||||
# Ensure that the date and timestamp saved correctly
|
||||
checkout = Checkout.objects.first()
|
||||
self.assertEqual(checkout.shop, shop)
|
||||
self.assertEqual(checkout.total, 10.00)
|
||||
self.assertEqual(checkout.currency, "USD")
|
||||
|
||||
def test_cannot_confirm_for_modelform_with_clean_field_overridden_if_validation_fails(
|
||||
self,
|
||||
):
|
||||
shop = ShopFactory()
|
||||
data = {
|
||||
"shop": shop.id,
|
||||
"currency": "USD",
|
||||
"total": "111",
|
||||
"date": str(timezone.now().date()),
|
||||
"timestamp_0": str(timezone.now().date()),
|
||||
"timestamp_1": str(timezone.now().time()),
|
||||
"_confirm_add": True,
|
||||
"_save": True,
|
||||
}
|
||||
response = self.client.post(reverse("admin:market_checkout_add"), data)
|
||||
|
||||
# Should not have been added yet
|
||||
self.assertEqual(Checkout.objects.count(), 0)
|
||||
|
||||
# Ensure not redirected (confirmation page does not redirect)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
expected_templates = [
|
||||
"admin/market/checkout/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_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))
|
||||
|
||||
def test_cannot_confirm_for_modelform_with_clean_overridden_if_validation_fails(
|
||||
self,
|
||||
):
|
||||
shop = ShopFactory()
|
||||
data = {
|
||||
"shop": shop.id,
|
||||
"currency": "USD",
|
||||
"total": "222",
|
||||
"date": str(timezone.now().date()),
|
||||
"timestamp_0": str(timezone.now().date()),
|
||||
"timestamp_1": str(timezone.now().time()),
|
||||
"_confirm_add": True,
|
||||
"_save": True,
|
||||
}
|
||||
response = self.client.post(reverse("admin:market_checkout_add"), data)
|
||||
|
||||
# Should not have been added yet
|
||||
self.assertEqual(Checkout.objects.count(), 0)
|
||||
|
||||
# Ensure not redirected (confirmation page does not redirect)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
expected_templates = [
|
||||
"admin/market/checkout/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_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))
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import factory
|
||||
|
||||
from random import choice, randint
|
||||
from django.utils import timezone
|
||||
|
||||
from tests.market.models import Item, Shop, Inventory
|
||||
from .market.models import Item, Shop, Inventory, Transaction
|
||||
from .market.constants import VALID_CURRENCIES
|
||||
|
||||
|
||||
class ItemFactory(factory.django.DjangoModelFactory):
|
||||
|
|
@ -11,7 +13,7 @@ class ItemFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
name = factory.Faker("name")
|
||||
price = factory.LazyAttribute(lambda _: randint(5, 500))
|
||||
currency = factory.LazyAttribute(lambda _: choice(Item.VALID_CURRENCIES))
|
||||
currency = "CAD"
|
||||
|
||||
|
||||
class ShopFactory(factory.django.DjangoModelFactory):
|
||||
|
|
@ -28,3 +30,14 @@ class InventoryFactory(factory.django.DjangoModelFactory):
|
|||
shop = factory.SubFactory(ShopFactory)
|
||||
item = factory.SubFactory(ItemFactory)
|
||||
quantity = factory.Sequence(lambda n: n)
|
||||
|
||||
|
||||
class TransactionFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Transaction
|
||||
|
||||
currency = "CAD"
|
||||
total = 0
|
||||
date = factory.LazyAttribute(lambda _: timezone.now().date())
|
||||
timestamp = factory.LazyAttribute(lambda _: timezone.now())
|
||||
shop = factory.SubFactory(ShopFactory)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,30 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from ..models import GeneralManager, Item, Inventory, Shop, ShoppingMall
|
||||
from ..models import (
|
||||
GeneralManager,
|
||||
Item,
|
||||
Inventory,
|
||||
ItemSale,
|
||||
Shop,
|
||||
ShoppingMall,
|
||||
Transaction,
|
||||
Checkout,
|
||||
)
|
||||
|
||||
from .item_admin import ItemAdmin
|
||||
from .inventory_admin import InventoryAdmin
|
||||
from .shop_admin import ShopAdmin
|
||||
from .shoppingmall_admin import ShoppingMallAdmin
|
||||
from .generalmanager_admin import GeneralManagerAdmin
|
||||
from .item_sale_admin import ItemSaleAdmin
|
||||
from .transaction_admin import TransactionAdmin
|
||||
from .checkout_admin import CheckoutAdmin
|
||||
|
||||
admin.site.register(Item, ItemAdmin)
|
||||
admin.site.register(Inventory, InventoryAdmin)
|
||||
admin.site.register(Shop, ShopAdmin)
|
||||
admin.site.register(ShoppingMall, ShoppingMallAdmin)
|
||||
admin.site.register(GeneralManager, GeneralManagerAdmin)
|
||||
admin.site.register(Transaction, TransactionAdmin)
|
||||
admin.site.register(ItemSale, ItemSaleAdmin)
|
||||
admin.site.register(Checkout, CheckoutAdmin)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from admin_confirm.admin import AdminConfirmMixin
|
||||
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.forms import ModelForm
|
||||
|
||||
from ..models import Checkout
|
||||
|
||||
|
||||
class CheckoutForm(ModelForm):
|
||||
class Meta:
|
||||
model = Checkout
|
||||
fields = [
|
||||
"currency",
|
||||
"shop",
|
||||
"total",
|
||||
"timestamp",
|
||||
"date",
|
||||
]
|
||||
|
||||
def clean_total(self):
|
||||
try:
|
||||
total = float(self.cleaned_data["total"])
|
||||
except:
|
||||
raise ValidationError("Invalid Total From clean_total")
|
||||
if total == 111: # Use to cause error in test
|
||||
raise ValidationError("Invalid Total 111")
|
||||
|
||||
return total
|
||||
|
||||
def clean(self):
|
||||
try:
|
||||
total = float(self.data["total"])
|
||||
except:
|
||||
raise ValidationError("Invalid Total From clean")
|
||||
if total == 222: # Use to cause error in test
|
||||
raise ValidationError("Invalid Total 222")
|
||||
|
||||
self.cleaned_data["total"] = total
|
||||
|
||||
|
||||
class CheckoutAdmin(AdminConfirmMixin, ModelAdmin):
|
||||
confirm_add = True
|
||||
confirm_change = True
|
||||
autocomplete_fields = ["shop"]
|
||||
form = CheckoutForm
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from admin_confirm.admin import AdminConfirmMixin
|
||||
|
||||
|
||||
from django.contrib.admin import ModelAdmin
|
||||
|
||||
|
||||
class ItemSaleAdmin(AdminConfirmMixin, ModelAdmin):
|
||||
confirm_add = True
|
||||
confirm_change = True
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from admin_confirm.admin import AdminConfirmMixin
|
||||
|
||||
|
||||
from django.contrib.admin import ModelAdmin
|
||||
|
||||
|
||||
class TransactionAdmin(AdminConfirmMixin, ModelAdmin):
|
||||
confirm_add = True
|
||||
confirm_change = True
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
VALID_CURRENCIES = (
|
||||
("CAD", "CAD"),
|
||||
("USD", "USD"),
|
||||
)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 3.1.7 on 2021-03-10 23:41
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('market', '0009_auto_20210304_0355'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Transaction',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('timestamp', models.DateField(auto_created=True)),
|
||||
('total', models.DecimalField(decimal_places=2, editable=False, max_digits=5)),
|
||||
('currency', models.CharField(choices=[('CAD', 'CAD'), ('USD', 'USD')], max_length=3)),
|
||||
('shop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='market.shop')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemSale',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('total', models.DecimalField(decimal_places=2, editable=False, max_digits=5)),
|
||||
('currency', models.CharField(choices=[('CAD', 'CAD'), ('USD', 'USD')], max_length=3)),
|
||||
('item', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='market.item')),
|
||||
('transaction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_sales', to='market.transaction')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Checkout',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('market.transaction',),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 3.1.7 on 2021-03-26 01:30
|
||||
|
||||
from django.db import migrations, models
|
||||
from ..validators import validate_currency
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("market", "0010_checkout_itemsale_transaction"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="itemsale",
|
||||
name="quantity",
|
||||
field=models.PositiveIntegerField(default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transaction",
|
||||
name="date",
|
||||
field=models.DateTimeField(default=None),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="itemsale",
|
||||
name="currency",
|
||||
field=models.CharField(max_length=5, validators=[validate_currency]),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.1.7 on 2021-03-26 02:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('market', '0011_auto_20210326_0130'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='transaction',
|
||||
name='date',
|
||||
field=models.DateField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transaction',
|
||||
name='timestamp',
|
||||
field=models.DateTimeField(auto_created=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||
from django.db.models.aggregates import Sum
|
||||
from django.db import models
|
||||
from .constants import VALID_CURRENCIES
|
||||
from .validators import validate_currency
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
VALID_CURRENCIES = (
|
||||
("CAD", "CAD"),
|
||||
("USD", "USD"),
|
||||
)
|
||||
# Because I'm lazy and don't want to update all test references
|
||||
VALID_CURRENCIES = VALID_CURRENCIES
|
||||
|
||||
name = models.CharField(max_length=120)
|
||||
price = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
currency = models.CharField(max_length=3, choices=VALID_CURRENCIES)
|
||||
|
|
@ -57,3 +60,48 @@ class ShoppingMall(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
total = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
currency = models.CharField(max_length=3, choices=VALID_CURRENCIES)
|
||||
shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
|
||||
timestamp = models.DateTimeField(auto_created=True)
|
||||
date = models.DateField()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class ItemSale(models.Model):
|
||||
transaction = models.ForeignKey(
|
||||
Transaction, on_delete=models.CASCADE, related_name="item_sales"
|
||||
)
|
||||
item = models.ForeignKey(Item, on_delete=models.SET_NULL, null=True)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
total = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
currency = models.CharField(max_length=5, validators=[validate_currency])
|
||||
|
||||
def clean(self):
|
||||
errors = {}
|
||||
# check that shop has the stock
|
||||
shop = self.transaction.shop
|
||||
inventory = Inventory.objects.filter(shop=shop, item=self.item)
|
||||
if not inventory:
|
||||
errors["item"] = "Shop does not have the item stocked"
|
||||
else:
|
||||
in_stock = inventory.aggregate(Sum("quantity")).get("quantity__sum", 0)
|
||||
if in_stock < self.quantity:
|
||||
errors["item"] = "Shop does not have enough of the item stocked"
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
||||
class Checkout(Transaction):
|
||||
"""
|
||||
Proxy Model to use in Django Admin to create a Transaction
|
||||
As if a customer was checking out at a physical checkout
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from .constants import VALID_CURRENCIES
|
||||
|
||||
|
||||
def validate_currency(value: str):
|
||||
currency_values = [c[0] for c in VALID_CURRENCIES]
|
||||
if value not in currency_values:
|
||||
raise ValidationError("Invalid Currency")
|
||||
Loading…
Reference in New Issue