Update admin. Fix some more tests. Add stock info to template.
This commit is contained in:
parent
c9ae322025
commit
ac68daf0b6
|
@ -90,6 +90,15 @@ def available_to(product):
|
||||||
available_to.short_description = 'Available to'
|
available_to.short_description = 'Available to'
|
||||||
|
|
||||||
|
|
||||||
|
def stock_info(product):
|
||||||
|
if product.stock_amount:
|
||||||
|
return "{} / {}".format(
|
||||||
|
product.left_in_stock,
|
||||||
|
product.stock_amount
|
||||||
|
)
|
||||||
|
return "N/A"
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
@admin.register(Product)
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
list_display = [
|
list_display = [
|
||||||
|
@ -98,6 +107,7 @@ class ProductAdmin(admin.ModelAdmin):
|
||||||
'ticket_type',
|
'ticket_type',
|
||||||
'price',
|
'price',
|
||||||
'description',
|
'description',
|
||||||
|
stock_info,
|
||||||
available_from,
|
available_from,
|
||||||
available_to
|
available_to
|
||||||
]
|
]
|
||||||
|
|
|
@ -349,10 +349,16 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
- Whether now is in the self.available_in
|
- Whether now is in the self.available_in
|
||||||
- If a stock is defined, that there are items left
|
- If a stock is defined, that there are items left
|
||||||
"""
|
"""
|
||||||
|
predicates = [self.is_time_available]
|
||||||
|
if self.stock_amount:
|
||||||
|
predicates.append(self.is_stock_available)
|
||||||
|
return all(predicates)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_time_available(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
time_available = now in self.available_in
|
time_available = now in self.available_in
|
||||||
stock_available = self.left_in_stock() > 0
|
return time_available
|
||||||
return time_available and stock_available
|
|
||||||
|
|
||||||
def is_old(self):
|
def is_old(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
@ -364,16 +370,26 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
return self.available_in.lower > now
|
return self.available_in.lower > now
|
||||||
|
|
||||||
|
@property
|
||||||
def left_in_stock(self):
|
def left_in_stock(self):
|
||||||
sold = OrderProductRelation.objects.filter(
|
if self.stock_amount:
|
||||||
product=self,
|
sold = OrderProductRelation.objects.filter(
|
||||||
order__paid=True,
|
product=self,
|
||||||
).aggregate(Sum('quantity'))['quantity__sum']
|
order__paid=True,
|
||||||
|
).aggregate(Sum('quantity'))['quantity__sum']
|
||||||
|
|
||||||
total_left = self.stock_amount - (sold or 0)
|
total_left = self.stock_amount - (sold or 0)
|
||||||
|
|
||||||
return total_left
|
return total_left
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_stock_available(self):
|
||||||
|
if self.stock_amount:
|
||||||
|
stock_available = self.left_in_stock > 0
|
||||||
|
return stock_available
|
||||||
|
# If there is no stock defined the product is generally available.
|
||||||
|
return True
|
||||||
|
|
||||||
class OrderProductRelation(CreatedUpdatedModel):
|
class OrderProductRelation(CreatedUpdatedModel):
|
||||||
order = models.ForeignKey('shop.Order', on_delete=models.PROTECT)
|
order = models.ForeignKey('shop.Order', on_delete=models.PROTECT)
|
||||||
|
|
|
@ -25,15 +25,21 @@
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{% if product.stock_amount %}
|
{% if product.stock_amount and product.is_time_available %}
|
||||||
<h3>
|
<h3>
|
||||||
<small>Availability</small><br />
|
<small>Availability</small><br />
|
||||||
|
{% if product.left_in_stock > 0 %}
|
||||||
<bold>{{ product.left_in_stock }}</bold> available<br />
|
<bold>{{ product.left_in_stock }}</bold> available<br />
|
||||||
|
{% else %}
|
||||||
|
<bold>Sold out.</bold>
|
||||||
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if product.is_stock_available %}
|
||||||
|
|
||||||
<h3>Add to order</h3>
|
<h3>Add to order</h3>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
@ -67,6 +73,8 @@
|
||||||
to order this product
|
to order this product
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,7 +47,6 @@ Shop | {{ block.super }}
|
||||||
</a>
|
</a>
|
||||||
</strong>
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endifchanged %}
|
{% endifchanged %}
|
||||||
|
|
||||||
|
@ -56,6 +55,11 @@ Shop | {{ block.super }}
|
||||||
<a href="{% url 'shop:product_detail' slug=product.slug %}">
|
<a href="{% url 'shop:product_detail' slug=product.slug %}">
|
||||||
{{ product.name }}
|
{{ product.name }}
|
||||||
</a>
|
</a>
|
||||||
|
{% if product.stock_amount and product.left_in_stock <= 10 %}
|
||||||
|
<div class="label label-danger">
|
||||||
|
Only {{ product.left_in_stock }} left!
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from psycopg2.extras import DateTimeTZRange
|
||||||
|
|
||||||
from .factories import (
|
from .factories import (
|
||||||
ProductFactory,
|
ProductFactory,
|
||||||
|
@ -13,8 +16,8 @@ class ProductAvailabilityTest(TestCase):
|
||||||
def test_product_available_by_stock(self):
|
def test_product_available_by_stock(self):
|
||||||
""" If no orders have been made, the product is still available. """
|
""" If no orders have been made, the product is still available. """
|
||||||
product = ProductFactory(stock_amount=10)
|
product = ProductFactory(stock_amount=10)
|
||||||
self.assertEqual(product.left_in_stock(), 10)
|
self.assertEqual(product.left_in_stock, 10)
|
||||||
self.assertTrue(product.is_available)
|
self.assertTrue(product.is_available())
|
||||||
|
|
||||||
def test_product_not_available_by_stock(self):
|
def test_product_not_available_by_stock(self):
|
||||||
""" If max orders have been made, the product is NOT available. """
|
""" If max orders have been made, the product is NOT available. """
|
||||||
|
@ -26,5 +29,48 @@ class ProductAvailabilityTest(TestCase):
|
||||||
order.paid = True
|
order.paid = True
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
self.assertEqual(product.left_in_stock(), 0)
|
self.assertEqual(product.left_in_stock, 0)
|
||||||
|
self.assertFalse(product.is_stock_available)
|
||||||
self.assertFalse(product.is_available())
|
self.assertFalse(product.is_available())
|
||||||
|
|
||||||
|
def test_product_available_by_time(self):
|
||||||
|
""" The product is available if now is in the right timeframe. """
|
||||||
|
product = ProductFactory()
|
||||||
|
# The factory defines the timeframe as now and 31 days forward.
|
||||||
|
self.assertTrue(product.is_time_available)
|
||||||
|
self.assertTrue(product.is_available())
|
||||||
|
|
||||||
|
def test_product_not_available_by_time(self):
|
||||||
|
""" The product is not available if now is outside the timeframe. """
|
||||||
|
available_in = DateTimeTZRange(
|
||||||
|
lower=timezone.now() - timezone.timedelta(5),
|
||||||
|
upper=timezone.now() - timezone.timedelta(1)
|
||||||
|
)
|
||||||
|
product = ProductFactory(available_in=available_in)
|
||||||
|
# The factory defines the timeframe as now and 31 days forward.
|
||||||
|
self.assertFalse(product.is_time_available)
|
||||||
|
self.assertFalse(product.is_available())
|
||||||
|
|
||||||
|
def test_product_is_not_available_yet(self):
|
||||||
|
""" The product is not available because we are before lower bound. """
|
||||||
|
available_in = DateTimeTZRange(
|
||||||
|
lower=timezone.now() + timezone.timedelta(5)
|
||||||
|
)
|
||||||
|
product = ProductFactory(available_in=available_in)
|
||||||
|
# Make sure there is no upper - just in case.
|
||||||
|
self.assertEqual(product.available_in.upper, None)
|
||||||
|
# The factory defines the timeframe as now and 31 days forward.
|
||||||
|
self.assertFalse(product.is_time_available)
|
||||||
|
self.assertFalse(product.is_available())
|
||||||
|
|
||||||
|
def test_product_is_available_from_now_on(self):
|
||||||
|
""" The product is available because we are after lower bound. """
|
||||||
|
available_in = DateTimeTZRange(
|
||||||
|
lower=timezone.now() - timezone.timedelta(1)
|
||||||
|
)
|
||||||
|
product = ProductFactory(available_in=available_in)
|
||||||
|
# Make sure there is no upper - just in case.
|
||||||
|
self.assertEqual(product.available_in.upper, None)
|
||||||
|
# The factory defines the timeframe as now and 31 days forward.
|
||||||
|
self.assertTrue(product.is_time_available)
|
||||||
|
self.assertTrue(product.is_available())
|
||||||
|
|
Loading…
Reference in a new issue