Formatted with black and reached 100% coverage

main
Thu Trang Pham 2020-11-08 09:51:49 -08:00
parent c586100098
commit a95383cfa2
17 changed files with 465 additions and 179 deletions

View File

@ -1,2 +1,11 @@
run:
./tests/manage.py runserver
test:
coverage run --branch -m pytest
coverage html
coverage-badge -f -o coverage.svg
migrate:
./tests/manage.py makemigrations
./tests/manage.py migrate

View File

@ -1,5 +1,7 @@
# Django Admin Confirm
![coverage](/coverage.svg)
AdminConfirmMixin is a mixin for ModelAdmin to add confirmations to changes and additions.
![Screenshot of Confirmation Page](/screenshot.png)

View File

@ -52,21 +52,23 @@ class AdminConfirmMixin:
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
if request.method == "POST":
if (not object_id and "_confirm_add" in request.POST) or (object_id and "_confirm_change" in request.POST):
return self._change_confirmation_view(request, object_id, form_url, extra_context)
if (not object_id and "_confirm_add" in request.POST) or (
object_id and "_confirm_change" in request.POST
):
return self._change_confirmation_view(
request, object_id, form_url, extra_context
)
extra_context = {
**(extra_context or {}),
'confirm_add': self.confirm_add,
'confirm_change': self.confirm_change
"confirm_add": self.confirm_add,
"confirm_change": self.confirm_change,
}
return super().changeform_view(request, object_id, form_url, extra_context)
def _change_confirmation_view(self, request, object_id, form_url, extra_context):
# This code is taken from super()._changeform_view
to_field = request.POST.get(
TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR)
)
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
if to_field and not self.to_field_allowed(request, to_field):
raise DisallowedModelAdminToField(
"The field %s cannot be referenced." % to_field
@ -83,7 +85,6 @@ class AdminConfirmMixin:
obj = None
else:
obj = self.get_object(request, unquote(object_id), to_field)
if obj is None:
return self._get_obj_does_not_exist_redirect(request, opts, object_id)
@ -105,21 +106,30 @@ class AdminConfirmMixin:
# End code from super()._changeform_view
changed_data = {}
if add:
for name in form.changed_data:
new_value = new_object.__getattribute__(name)
if new_value is not None:
changed_data[name] = [None, new_value]
else:
# Parse the changed data - Note that using form.changed_data would not work because initial is not set
for name, field in form.fields.items():
initial_value = obj.__getattribute__(name)
new_value = new_object.__getattribute__(name)
if field.has_changed(initial_value, new_value) and initial_value != new_value:
changed_data[name] = [initial_value, new_value]
if form_validated:
if add:
for name in form.changed_data:
new_value = getattr(new_object, name)
# Don't consider default values as changed for adding
if (
new_value is not None
and new_value != model._meta.get_field(name).default
):
changed_data[name] = [None, new_value]
else:
# Parse the changed data - Note that using form.changed_data would not work because initial is not set
for name, field in form.fields.items():
initial_value = getattr(obj, name)
new_value = getattr(new_object, name)
if (
field.has_changed(initial_value, new_value)
and initial_value != new_value
):
changed_data[name] = [initial_value, new_value]
changed_confirmation_fields = set(self.get_confirmation_fields(
request, obj)) & set(changed_data.keys())
changed_confirmation_fields = set(
self.get_confirmation_fields(request, obj)
) & set(changed_data.keys())
if not bool(changed_confirmation_fields):
# No confirmation required for changed fields, continue to save
return super()._changeform_view(request, object_id, form_url, extra_context)
@ -132,14 +142,14 @@ class AdminConfirmMixin:
if key in ["_save", "_saveasnew", "_addanother", "_continue"]:
save_action = key
if key.startswith("_") or key == 'csrfmiddlewaretoken':
if key.startswith("_") or key == "csrfmiddlewaretoken":
continue
form_data[key] = request.POST.get(key)
if add:
title_action = _('adding')
title_action = _("adding")
else:
title_action = _('changing')
title_action = _("changing")
context = {
**self.admin_site.each_context(request),

View File

@ -1,135 +1,303 @@
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from tests.market.admin import ItemAdmin
from tests.market.models import Item, Inventory
from django.contrib.admin.options import TO_FIELD_VAR
from django.http import HttpResponseForbidden, HttpResponseBadRequest
from django.urls import reverse
from tests.factories import ItemFactory, ShopFactory
from tests.market.admin import ItemAdmin, InventoryAdmin
from tests.market.models import Item, Inventory
from tests.factories import ItemFactory, ShopFactory, InventoryFactory
class TestAdminConfirmMixin(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
username='super', email='super@email.org', password='pass')
username="super", email="super@email.org", password="pass"
)
def setUp(self):
self.client.force_login(self.superuser)
self.factory = RequestFactory()
def test_get_add_without_confirm_add(self):
response = self.client.get(reverse('admin:market_item_add'))
self.assertFalse(response.context_data.get('confirm_add'))
self.assertNotIn('_confirm_add', response.rendered_content)
response = self.client.get(reverse("admin:market_item_add"))
self.assertFalse(response.context_data.get("confirm_add"))
self.assertNotIn("_confirm_add", response.rendered_content)
def test_get_add_with_confirm_add(self):
response = self.client.get(reverse('admin:market_inventory_add'))
self.assertTrue(response.context_data.get('confirm_add'))
self.assertIn('_confirm_add', response.rendered_content)
response = self.client.get(reverse("admin:market_inventory_add"))
self.assertTrue(response.context_data.get("confirm_add"))
self.assertIn("_confirm_add", response.rendered_content)
def test_get_change_without_confirm_change(self):
response = self.client.get(reverse('admin:market_shop_add'))
self.assertFalse(response.context_data.get('confirm_change'))
self.assertNotIn('_confirm_change', response.rendered_content)
response = self.client.get(reverse("admin:market_shop_add"))
self.assertFalse(response.context_data.get("confirm_change"))
self.assertNotIn("_confirm_change", response.rendered_content)
def test_get_change_with_confirm_change(self):
response = self.client.get(reverse('admin:market_inventory_add'))
self.assertTrue(response.context_data.get('confirm_change'))
self.assertIn('_confirm_change', response.rendered_content)
response = self.client.get(reverse("admin:market_inventory_add"))
self.assertTrue(response.context_data.get("confirm_change"))
self.assertIn("_confirm_change", response.rendered_content)
def test_post_add_without_confirm_add(self):
data = {'name': 'name', 'price': 2.0,
'currency': Item.VALID_CURRENCIES[0]}
response = self.client.post(reverse('admin:market_item_add'), data)
data = {"name": "name", "price": 2.0, "currency": Item.VALID_CURRENCIES[0]}
response = self.client.post(reverse("admin:market_item_add"), data)
# Redirects to item changelist and item is added
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/admin/market/item/')
self.assertEqual(response.url, "/admin/market/item/")
self.assertEqual(Item.objects.count(), 1)
def test_post_add_with_confirm_add(self):
item = ItemFactory()
shop = ShopFactory()
data = {'shop': shop.id, 'item': item.id,
'quantity': 5, '_confirm_add': True}
response = self.client.post(
reverse('admin:market_inventory_add'), data)
data = {"shop": shop.id, "item": item.id, "quantity": 5, "_confirm_add": True}
response = self.client.post(reverse("admin:market_inventory_add"), data)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
expected_templates = [
'admin/market/inventory/change_confirmation.html',
'admin/market/change_confirmation.html',
'admin/change_confirmation.html'
"admin/market/inventory/change_confirmation.html",
"admin/market/change_confirmation.html",
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
form_data = {'shop': str(shop.id), 'item': str(
item.id), 'quantity': str(5)}
self.assertEqual(
response.context_data['form_data'], form_data)
form_data = {"shop": str(shop.id), "item": str(item.id), "quantity": str(5)}
self.assertEqual(response.context_data["form_data"], form_data)
for k, v in form_data.items():
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">', response.rendered_content)
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)
# Should not have been added yet
self.assertEqual(Inventory.objects.count(), 0)
def test_post_change_with_confirm_change(self):
item = ItemFactory(name='item')
data = {'name': 'name', 'price': 2.0,
'currency': Item.VALID_CURRENCIES[0], '_confirm_change': True}
response = self.client.post(
f'/admin/market/item/{item.id}/change/', data)
item = ItemFactory(name="item")
data = {
"name": "name",
"price": 2.0,
"currency": Item.VALID_CURRENCIES[0],
"id": item.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
"_save": True,
}
response = self.client.post(f"/admin/market/item/{item.id}/change/", data)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 200)
expected_templates = [
'admin/market/item/change_confirmation.html',
'admin/market/change_confirmation.html',
'admin/change_confirmation.html'
"admin/market/item/change_confirmation.html",
"admin/market/change_confirmation.html",
"admin/change_confirmation.html",
]
self.assertEqual(response.template_name, expected_templates)
form_data = {'name': 'name', 'price': str(2.0),
'currency': Item.VALID_CURRENCIES[0][0]}
self.assertEqual(
response.context_data['form_data'], form_data)
form_data = {
"name": "name",
"price": str(2.0),
"id": str(item.id),
"currency": Item.VALID_CURRENCIES[0][0],
}
self.assertEqual(response.context_data["form_data"], form_data)
for k, v in form_data.items():
self.assertIn(
f'<input type="hidden" name="{ k }" value="{ v }">', response.rendered_content)
f'<input type="hidden" name="{ k }" value="{ v }">',
response.rendered_content,
)
# Hasn't changed item yet
item.refresh_from_db()
self.assertEqual(item.name, 'item')
self.assertEqual(item.name, "item")
def test_post_change_without_confirm_change(self):
shop = ShopFactory(name='bob')
data = {'name': 'sally'}
response = self.client.post(
f'/admin/market/shop/{shop.id}/change/', data)
shop = ShopFactory(name="bob")
data = {"name": "sally"}
response = self.client.post(f"/admin/market/shop/{shop.id}/change/", data)
# Redirects to changelist
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/admin/market/shop/')
self.assertEqual(response.url, "/admin/market/shop/")
# Shop has changed
shop.refresh_from_db()
self.assertEqual(shop.name, 'sally')
self.assertEqual(shop.name, "sally")
def test_get_confirmation_fields_should_default_if_not_set(self):
expected_fields = [f.name for f in Item._meta.fields if f.name != 'id']
expected_fields = [f.name for f in Item._meta.fields if f.name != "id"]
ItemAdmin.confirmation_fields = None
admin = ItemAdmin(Item, AdminSite())
actual_fields = admin.get_confirmation_fields(self.factory.request())
self.assertEqual(expected_fields, actual_fields)
def test_get_confirmation_fields_if_set(self):
expected_fields = ['name', 'currency']
expected_fields = ["name", "currency"]
ItemAdmin.confirmation_fields = expected_fields
admin = ItemAdmin(Item, AdminSite())
actual_fields = admin.get_confirmation_fields(self.factory.request())
self.assertEqual(expected_fields, actual_fields)
def test_custom_template(self):
expected_template = 'market/admin/my_custom_template.html'
expected_template = "market/admin/my_custom_template.html"
ItemAdmin.confirmation_template = expected_template
admin = ItemAdmin(Item, AdminSite())
actual_template = admin.render_change_confirmation(
self.factory.request(), context={}).template_name
self.factory.request(), context={}
).template_name
self.assertEqual(expected_template, actual_template)
ItemAdmin.confirmation_template = None
def test_form_invalid(self):
self.assertEqual(InventoryAdmin.confirmation_fields, ["quantity"])
inventory = InventoryFactory(quantity=1)
data = {
"quantity": 1,
"shop": "Invalid value",
"item": "Invalid value",
"id": inventory.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
}
response = self.client.post(
f"/admin/market/inventory/{inventory.id}/change/", data
)
# Form invalid should show erros on form
self.assertEqual(response.status_code, 200)
print(response.rendered_content)
self.assertIsNotNone(response.context_data.get("errors"))
self.assertEqual(
response.context_data["errors"][0],
["Select a valid choice. That choice is not one of the available choices."],
)
# Should not have updated inventory
inventory.refresh_from_db()
self.assertEqual(inventory.quantity, 1)
def test_confirmation_fields_set_with_confirm_change(self):
self.assertEqual(InventoryAdmin.confirmation_fields, ["quantity"])
inventory = InventoryFactory()
another_shop = ShopFactory()
data = {
"quantity": inventory.quantity,
"id": inventory.id,
"item": inventory.item.id,
"shop": another_shop.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
}
response = self.client.post(
f"/admin/market/inventory/{inventory.id}/change/", data
)
# Should not have shown confirmation page since shop did not change
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("admin:market_inventory_changelist"))
# Should have updated inventory
inventory.refresh_from_db()
self.assertEqual(inventory.shop, another_shop)
def test_confirmation_fields_set_with_confirm_add(self):
self.assertEqual(InventoryAdmin.confirmation_fields, ["quantity"])
item = ItemFactory()
shop = ShopFactory()
# Don't set quantity - let it default
data = {"shop": shop.id, "item": item.id, "_confirm_add": True}
response = self.client.post(reverse("admin:market_inventory_add"), data)
# No confirmation needed
self.assertEqual(response.status_code, 302)
# Should have been added
self.assertEqual(Inventory.objects.count(), 1)
new_inventory = Inventory.objects.all().first()
self.assertEqual(new_inventory.shop, shop)
self.assertEqual(new_inventory.item, item)
self.assertEqual(
new_inventory.quantity, Inventory._meta.get_field("quantity").default
)
def test_no_change_permissions(self):
user = User.objects.create_user(username="user", is_staff=True)
self.client.force_login(user)
inventory = InventoryFactory()
data = {
"quantity": 1000,
"id": inventory.id,
"item": inventory.item.id,
"shop": inventory.shop.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
}
response = self.client.post(
f"/admin/market/inventory/{inventory.id}/change/", data
)
self.assertEqual(response.status_code, 403)
self.assertTrue(isinstance(response, HttpResponseForbidden))
old_quantity = inventory.quantity
inventory.refresh_from_db()
self.assertEqual(inventory.quantity, old_quantity)
def test_no_add_permissions(self):
user = User.objects.create_user(username="user", is_staff=True)
self.client.force_login(user)
item = ItemFactory()
shop = ShopFactory()
data = {"shop": shop.id, "item": item.id, "quantity": 5, "_confirm_add": True}
response = self.client.post(reverse("admin:market_inventory_add"), data)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 403)
self.assertTrue(isinstance(response, HttpResponseForbidden))
# Should not have been added
self.assertEqual(Inventory.objects.count(), 0)
def test_obj_not_found(self):
inventory = InventoryFactory()
data = {
"quantity": 1000,
"id": 100,
"item": inventory.item.id,
"shop": inventory.shop.id,
"_confirm_change": True,
"csrfmiddlewaretoken": "fake token",
}
response = self.client.post("/admin/market/inventory/100/change/", data)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/")
self.assertEqual(response.reason_phrase, "Found")
old_quantity = inventory.quantity
inventory.refresh_from_db()
self.assertEqual(inventory.quantity, old_quantity)
self.assertEqual(Inventory.objects.count(), 1)
def test_handles_to_field_not_allowed(self):
item = ItemFactory()
shop = ShopFactory()
data = {
"shop": shop.id,
"item": item.id,
"quantity": 5,
"_confirm_add": True,
TO_FIELD_VAR: "shop",
}
response = self.client.post(reverse("admin:market_inventory_add"), data)
# Ensure not redirected (confirmation page does not redirect)
self.assertEqual(response.status_code, 400)
self.assertTrue(isinstance(response, HttpResponseBadRequest))
self.assertEqual(response.reason_phrase, "Bad Request")
self.assertEqual(
response.context.get("exception_value"),
"The field shop cannot be referenced.",
)
# Should not have been added
self.assertEqual(Inventory.objects.count(), 0)

21
coverage.svg 100644
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="99" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="#555" d="M0 0h63v20H0z"/>
<path fill="#4c1" d="M63 0h36v20H63z"/>
<path fill="url(#b)" d="M0 0h99v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="31.5" y="14">coverage</text>
<text x="80" y="15" fill="#010101" fill-opacity=".3">100%</text>
<text x="80" y="14">100%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 903 B

View File

@ -2,19 +2,19 @@ import os
from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.md')).read()
README = open(os.path.join(here, "README.md")).read()
setup(
name='django-admin-confirm',
version='0.1',
packages=['admin_confirm'],
description='Adds confirmation to Django Admin changes and additions',
name="django-admin-confirm",
version="0.1",
packages=["admin_confirm"],
description="Adds confirmation to Django Admin changes and additions",
long_description=README,
author='Thu Trang Pham',
author_email='thuutrangpham@gmail.com',
url='https://github.com/trangpham/django-admin-confirm/',
license='Apache 2.0',
author="Thu Trang Pham",
author_email="thuutrangpham@gmail.com",
url="https://github.com/trangpham/django-admin-confirm/",
license="Apache 2.0",
install_requires=[
'Django>=1.7',
]
)
"Django>=1.7",
],
)

View File

@ -9,7 +9,7 @@ class ItemFactory(factory.django.DjangoModelFactory):
class Meta:
model = Item
name = factory.Faker('name')
name = factory.Faker("name")
price = factory.LazyAttribute(lambda _: randint(5, 500))
currency = factory.LazyAttribute(lambda _: choice(Item.VALID_CURRENCIES))
@ -18,7 +18,7 @@ class ShopFactory(factory.django.DjangoModelFactory):
class Meta:
model = Shop
name = factory.Faker('name')
name = factory.Faker("name")
class InventoryFactory(factory.django.DjangoModelFactory):
@ -28,4 +28,3 @@ class InventoryFactory(factory.django.DjangoModelFactory):
shop = factory.SubFactory(ShopFactory)
item = factory.SubFactory(ItemFactory)
quantity = factory.Sequence(lambda n: n)

View File

@ -5,7 +5,7 @@ import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -17,5 +17,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -6,19 +6,19 @@ from .models import Item, Inventory, Shop
class ItemAdmin(AdminConfirmMixin, admin.ModelAdmin):
list_display = ('name', 'price', 'currency')
list_display = ("name", "price", "currency")
confirm_change = True
class InventoryAdmin(AdminConfirmMixin, admin.ModelAdmin):
list_display = ('shop', 'item', 'quantity')
list_display = ("shop", "item", "quantity")
confirm_change = True
confirm_add = True
confirmation_fields = ['shop']
confirmation_fields = ["quantity"]
class ShopAdmin(AdminConfirmMixin, admin.ModelAdmin):
confirmation_fields = ['name']
confirmation_fields = ["name"]
admin.site.register(Item, ItemAdmin)

View File

@ -2,4 +2,4 @@ from django.apps import AppConfig
class MarketConfig(AppConfig):
name = 'market'
name = "market"

View File

@ -8,25 +8,52 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Item',
name="Item",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('currency', models.CharField(choices=[('CAD', 'CAD'), ('USD', 'USD')], max_length=3)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=120)),
("price", models.DecimalField(decimal_places=2, max_digits=5)),
(
"currency",
models.CharField(
choices=[("CAD", "CAD"), ("USD", "USD")], max_length=3
),
),
],
),
migrations.CreateModel(
name='Stock',
name="Stock",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField()),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='all_stock', to='market.Item')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("quantity", models.PositiveIntegerField()),
(
"item",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="all_stock",
to="market.Item",
),
),
],
),
]

View File

@ -7,38 +7,63 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('market', '0001_initial'),
("market", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Inventory',
name="Inventory",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField()),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='market.Item')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("quantity", models.PositiveIntegerField()),
(
"item",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="market.Item"
),
),
],
options={
'ordering': ['shop', 'item__name'],
"ordering": ["shop", "item__name"],
},
),
migrations.CreateModel(
name='Shop',
name="Shop",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=120)),
],
),
migrations.DeleteModel(
name='Stock',
name="Stock",
),
migrations.AddField(
model_name='inventory',
name='shop',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory', to='market.Shop'),
model_name="inventory",
name="shop",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="inventory",
to="market.Shop",
),
),
migrations.AlterUniqueTogether(
name='inventory',
unique_together={('shop', 'item')},
name="inventory",
unique_together={("shop", "item")},
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.0.10 on 2020-11-08 17:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("market", "0002_auto_20201031_2057"),
]
operations = [
migrations.AlterModelOptions(
name="inventory",
options={
"ordering": ["shop", "item__name"],
"verbose_name_plural": "Inventory",
},
),
migrations.AlterField(
model_name="inventory",
name="quantity",
field=models.PositiveIntegerField(blank=True, default=0, null=True),
),
]

View File

@ -3,8 +3,8 @@ from django.db import models
class Item(models.Model):
VALID_CURRENCIES = (
('CAD', 'CAD'),
('USD', 'USD'),
("CAD", "CAD"),
("USD", "USD"),
)
name = models.CharField(max_length=120)
price = models.DecimalField(max_digits=5, decimal_places=2)
@ -23,10 +23,12 @@ class Shop(models.Model):
class Inventory(models.Model):
class Meta:
unique_together = ['shop', 'item']
ordering = ['shop', 'item__name']
verbose_name_plural = 'Inventory'
unique_together = ["shop", "item"]
ordering = ["shop", "item__name"]
verbose_name_plural = "Inventory"
shop = models.ForeignKey(to=Shop, on_delete=models.CASCADE, related_name='inventory')
shop = models.ForeignKey(
to=Shop, on_delete=models.CASCADE, related_name="inventory"
)
item = models.ForeignKey(to=Item, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
quantity = models.PositiveIntegerField(default=0, null=True, blank=True)

View File

@ -19,67 +19,65 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '=yddl-40388w3e2hl$e8)revce=n67_idi8pfejtn3!+2%!_qt'
SECRET_KEY = "=yddl-40388w3e2hl$e8)revce=n67_idi8pfejtn3!+2%!_qt"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1']
ALLOWED_HOSTS = ["127.0.0.1"]
# Application definition
INSTALLED_APPS = [
'admin_confirm',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tests.market',
"admin_confirm",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"tests.market",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = 'tests.test_project.urls'
ROOT_URLCONF = "tests.test_project.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'tests.test_project.wsgi.application'
WSGI_APPLICATION = "tests.test_project.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
@ -89,16 +87,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@ -106,9 +104,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"
USE_I18N = True
@ -120,4 +118,4 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_URL = "/static/"

View File

@ -2,5 +2,5 @@ from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
path("admin/", admin.site.urls),
]

View File

@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
application = get_wsgi_application()