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
Thu Trang Pham 2021-03-31 12:46:02 -07:00 committed by GitHub
parent c88eb16a9b
commit 4d6b2900d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 763 additions and 11 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
VALID_CURRENCIES = (
("CAD", "CAD"),
("USD", "USD"),
)

View File

@ -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',),
),
]

View File

@ -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]),
),
]

View File

@ -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),
),
]

View File

@ -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

View File

@ -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")