from django.utils.translation import ugettext_lazy as _


class Base(object):
    """
    Base availability policy.
    """

    #: Availability code.  This is used for HTML classes
    code = ''

    #: A description of the availability of a product.  This is shown on the
    #: product detail page.  Eg "In stock", "Out of stock" etc
    message = ''

    #: When this item should be dispatched
    dispatch_date = None

    @property
    def short_message(self):
        """
        A shorter version of the availability message, suitable for showing on
        browsing pages.
        """
        return self.message

    @property
    def is_available_to_buy(self):
        """
        Test if this product is available to be bought.  This is used for
        validation when a product is added to a user's basket.
        """
        # We test a purchase of a single item
        return self.is_purchase_permitted(1)[0]

    def is_purchase_permitted(self, quantity):
        """
        Test whether a proposed purchase is allowed

        Should return a boolean and a reason
        """
        return False, _("unavailable")


# Common availability policies


class Unavailable(Base):
    """
    Policy for when a product is unavailable
    """
    code = 'unavailable'
    message = _("Unavailable")


class Available(Base):
    """
    For when a product is always available, irrespective of stock level.

    This might be appropriate for digital products where stock doesn't need to
    be tracked and the product is always available to buy.
    """
    code = 'available'
    message = _("Available")

    def is_purchase_permitted(self, quantity):
        return True, ""


class StockRequired(Base):
    """
    Allow a product to be bought while there is stock.  This policy is
    instantiated with a stock number (``num_available``).  It ensures that the
    product is only available to buy while there is stock available.

    This is suitable for physical products where back orders (eg allowing
    purchases when there isn't stock available) are not permitted.
    """
    CODE_IN_STOCK = 'instock'
    CODE_OUT_OF_STOCK = 'outofstock'

    def __init__(self, num_available):
        self.num_available = num_available

    def is_purchase_permitted(self, quantity):
        if self.num_available == 0:
            return False, _("no stock available")
        if quantity > self.num_available:
            msg = _("a maximum of %(max)d can be bought") % {
                'max': self.num_available}
            return False, msg
        return True, ""

    @property
    def code(self):
        if self.num_available > 0:
            return self.CODE_IN_STOCK
        return self.CODE_OUT_OF_STOCK

    @property
    def short_message(self):
        if self.num_available > 0:
            return _("In stock")
        return _("Unavailable")

    @property
    def message(self):
        if self.num_available > 0:
            return _("In stock (%d available)") % self.num_available
        return _("Unavailable")

src/oscar/apps/partner/availability.py
from django.utils.translation import ugettext_lazy as _


class Base(object):
    """
    Base availability policy.
    """

    #: Availability code.  This is used for HTML classes
    code = ''

    #: A description of the availability of a product.  This is shown on the
    #: product detail page.  Eg "In stock", "Out of stock" etc
    message = ''

    #: When this item should be dispatched
    dispatch_date = None

    @property
    def short_message(self):
        """
        A shorter version of the availability message, suitable for showing on
        browsing pages.
        """
        return self.message

    @property
    def is_available_to_buy(self):
        """
        Test if this product is available to be bought.  This is used for
        validation when a product is added to a user's basket.
        """
        # We test a purchase of a single item
        return self.is_purchase_permitted(1)[0]

    def is_purchase_permitted(self, quantity):
        """
        Test whether a proposed purchase is allowed

        Should return a boolean and a reason
        """
        return False, _("unavailable")


# Common availability policies


class Unavailable(Base):
    """
    Policy for when a product is unavailable
    """
    code = 'unavailable'
    message = _("Unavailable")


class Available(Base):
    """
    For when a product is always available, irrespective of stock level.

    This might be appropriate for digital products where stock doesn't need to
    be tracked and the product is always available to buy.
    """
    code = 'available'
    message = _("Available")

    def is_purchase_permitted(self, quantity):
        return True, ""


class StockRequired(Base):
    """
    Allow a product to be bought while there is stock.  This policy is
    instantiated with a stock number (``num_available``).  It ensures that the
    product is only available to buy while there is stock available.

    This is suitable for physical products where back orders (eg allowing
    purchases when there isn't stock available) are not permitted.
    """
    CODE_IN_STOCK = 'instock'
    CODE_OUT_OF_STOCK = 'outofstock'

    def __init__(self, num_available):
        self.num_available = num_available

    def is_purchase_permitted(self, quantity):
        if self.num_available <= 0:
            return False, _("no stock available")
        if quantity > self.num_available:
            msg = _("a maximum of %(max)d can be bought") % {
                'max': self.num_available}
            return False, msg
        return True, ""

    @property
    def code(self):
        if self.num_available > 0:
            return self.CODE_IN_STOCK
        return self.CODE_OUT_OF_STOCK

    @property
    def short_message(self):
        if self.num_available > 0:
            return _("In stock")
        return _("Unavailable")

    @property
    def message(self):
        if self.num_available > 0:
            return _("In stock (%d available)") % self.num_available
        return _("Unavailable")

src/oscar/apps/partner/availability.py
diff --git a/src/oscar/apps/partner/availability.py b/src/oscar/apps/partner/availability.py
index a999f2dce96..eaa3b6e9a17 100644
--- a/src/oscar/apps/partner/availability.py
+++ b/src/oscar/apps/partner/availability.py
@@ -83,7 +83,7 @@ def __init__(self, num_available):
         self.num_available = num_available
 
     def is_purchase_permitted(self, quantity):
-        if self.num_available == 0:
+        if self.num_available <= 0:
             return False, _("no stock available")
         if quantity > self.num_available:
             msg = _("a maximum of %(max)d can be bought") % {
diff --git a/tests/unit/partner/availability_tests.py b/tests/unit/partner/availability_tests.py
index 9629b0d3247..93966b577c6 100644
--- a/tests/unit/partner/availability_tests.py
+++ b/tests/unit/partner/availability_tests.py
@@ -51,8 +51,9 @@ def test_permits_purchases_up_to_stock_level(self):
             self.assertTrue(is_permitted)
 
     def test_forbids_purchases_over_stock_level(self):
-        is_permitted, __ = self.availability.is_purchase_permitted(7)
+        is_permitted, msg = self.availability.is_purchase_permitted(7)
         self.assertFalse(is_permitted)
+        self.assertEqual(msg, "a maximum of 5 can be bought")
 
     def test_returns_correct_code(self):
         self.assertEqual('instock', self.availability.code)
@@ -60,6 +61,13 @@ def test_returns_correct_code(self):
     def test_returns_correct_message(self):
         self.assertEqual('In stock (5 available)', self.availability.message)
 
+    def test_returns_correct_message_when_allocation_higher_than_stock(self):
+        # this is the value passed when stock lower than allocation by 1
+        self.availability.num_available = -1
+        is_permitted, msg = self.availability.is_purchase_permitted(1)
+        self.assertFalse(is_permitted)
+        self.assertEqual(msg, "no stock available")
+
 
 class TestStockRequiredWrapperForRecordWithoutStock(TestCase):
 

In cases where the stock is lower than the number of allocated products incorrect message appears
To reproduce:
1. Add 2 products to basket
2. On one of them set num of allocated products as 1 and stock as 0.
3. On the basket page try to do an operation on the one that is still in stock. The message that appears is "a maximum of -1 can be bought" instead of the "no stock available" message

