Repository: django-oscar/django-oscar

Issue:
ConditionalOffer Priority ignored?
As i wrote here (https://groups.google.com/forum/#!msg/django-oscar/TdQAW_3QAjk/oAlRKQ-F5zMJ) the priority of the conditional offer seems to be ignored.
It has to be considered in this function

``` python
def get_site_offers(self):
        """
        Return site offers that are available to all users
        """
        # Using select_related with the condition/benefit ranges doesn't seem
        # to work.  I think this is because both the related objects have the
        # FK to range with the same name.
        date_based_offers = ConditionalOffer.active.select_related(
            'condition', 'condition__range', 'benefit').filter(
                offer_type=ConditionalOffer.SITE,
                status=ConditionalOffer.OPEN)
        nondate_based_offers = ConditionalOffer.objects.select_related(
            'condition', 'condition__range', 'benefit').filter(
                offer_type=ConditionalOffer.SITE,
                status=ConditionalOffer.OPEN,
                start_datetime=None, end_datetime=None)
        return list(chain(date_based_offers, nondate_based_offers))
```

so i replaced it with:

``` python
def get_site_offers(self):
        """
        Return site offers ordered form priority that are available to all users
        """
        # Using select_related with the condition/benefit ranges doesn't seem
        # to work.  I think this is because both the related objects have the
        # FK to range with the same name.
        date_based_offers = ConditionalOffer.active.select_related(
            'condition', 'condition__range', 'benefit').filter(
                offer_type=ConditionalOffer.SITE,
                status=ConditionalOffer.OPEN)
        date_based_offers_ids =list( date_based_offers.values_list('id',flat=True))
        nondate_based_offers = ConditionalOffer.objects.select_related(
            'condition', 'condition__range', 'benefit').filter(
                offer_type=ConditionalOffer.SITE,
                status=ConditionalOffer.OPEN,
                start_datetime=None, end_datetime=None)
        nondate_based_offers_ids = list(nondate_based_offers.values_list('id',flat=True))
        ordered_offers = ConditionalOffer.active.filter(id__in=date_based_offers_ids+nondate_based_offers_ids).order_by('-priority')
        return ordered_offers
```

The problem is that  the `list(chain())` call in the method  `get_offers`   will shuffle the queryset if there will be  more offers' type .



Discussion:

Resolving patch:
diff --git a/oscar/apps/offer/utils.py b/oscar/apps/offer/utils.py
--- a/oscar/apps/offer/utils.py
+++ b/oscar/apps/offer/utils.py
@@ -1,7 +1,8 @@
 from itertools import chain
 import logging
 
-from django.db.models import get_model
+from django.db.models import get_model, Q
+from django.utils.timezone import now
 
 from oscar.apps.offer import results
 
@@ -66,19 +67,22 @@ def get_site_offers(self):
         """
         Return site offers that are available to all users
         """
+        cutoff = now()
+        date_based = Q(
+            Q(start_datetime__lte=cutoff),
+            Q(end_datetime__gte=cutoff) | Q(end_datetime=None),
+        )
+
+        nondate_based = Q(start_datetime=None, end_datetime=None)
+
+        qs = ConditionalOffer.objects.filter(
+            date_based | nondate_based,
+            offer_type=ConditionalOffer.SITE,
+            status=ConditionalOffer.OPEN)
         # Using select_related with the condition/benefit ranges doesn't seem
         # to work.  I think this is because both the related objects have the
         # FK to range with the same name.
-        date_based_offers = ConditionalOffer.active.select_related(
-            'condition', 'condition__range', 'benefit').filter(
-                offer_type=ConditionalOffer.SITE,
-                status=ConditionalOffer.OPEN)
-        nondate_based_offers = ConditionalOffer.objects.select_related(
-            'condition', 'condition__range', 'benefit').filter(
-                offer_type=ConditionalOffer.SITE,
-                status=ConditionalOffer.OPEN,
-                start_datetime=None, end_datetime=None)
-        return list(chain(date_based_offers, nondate_based_offers))
+        return qs.select_related('condition', 'condition__range', 'benefit')
 
     def get_basket_offers(self, basket, user):
         """


Test patch:
diff --git a/oscar/test/factories.py b/oscar/test/factories.py
--- a/oscar/test/factories.py
+++ b/oscar/test/factories.py
@@ -132,25 +132,26 @@ def create_order(number=None, basket=None, user=None, shipping_address=None,
 
 def create_offer(name="Dummy offer", offer_type="Site",
                  max_basket_applications=None, range=None, condition=None,
-                 benefit=None):
+                 benefit=None, priority=0):
     """
     Helper method for creating an offer
     """
     if range is None:
-        range = models.Range.objects.create(name="All products range",
-                                            includes_all_products=True)
+        range, __ = models.Range.objects.get_or_create(
+            name="All products range", includes_all_products=True)
     if condition is None:
-        condition = models.Condition.objects.create(
+        condition, __ = models.Condition.objects.get_or_create(
             range=range, type=models.Condition.COUNT, value=1)
     if benefit is None:
-        benefit = models.Benefit.objects.create(
+        benefit, __ = models.Benefit.objects.get_or_create(
             range=range, type=models.Benefit.PERCENTAGE, value=20)
     return models.ConditionalOffer.objects.create(
         name=name,
         offer_type=offer_type,
         condition=condition,
         benefit=benefit,
-        max_basket_applications=max_basket_applications)
+        max_basket_applications=max_basket_applications,
+        priority=priority)
 
 
 def create_voucher():
diff --git a/tests/integration/offer/priority_offers_tests.py b/tests/integration/offer/priority_offers_tests.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/offer/priority_offers_tests.py
@@ -0,0 +1,47 @@
+import datetime
+
+from django.test import TestCase
+from django.db.models import get_model
+import mock
+
+from oscar.apps.offer import utils
+from oscar.test import factories
+
+Voucher = get_model('voucher', 'Voucher')
+
+class TestPriorityOffers(TestCase):
+    def test_site_offers_are_ordered(self):
+        factories.create_offer(name="A", priority=0)
+        factories.create_offer(name="B", priority=7)
+        factories.create_offer(name="C", priority=5)
+        factories.create_offer(name="D", priority=7)
+        factories.create_offer(name="E", priority=1)
+
+        offers = utils.Applicator().get_site_offers()
+        ordered_names = [offer.name for offer in offers]
+        self.assertEqual(["B", "D", "C", "E", "A"], ordered_names)
+
+    def test_basket_offers_are_ordered(self):
+        voucher = Voucher.objects.create(
+            name="Test voucher",
+            code="test",
+            start_date=datetime.date.today(),
+            end_date=datetime.date.today() + datetime.timedelta(days=12))
+
+        voucher.offers = [
+            factories.create_offer(name="A", priority=0),
+            factories.create_offer(name="B", priority=7),
+            factories.create_offer(name="C", priority=5),
+            factories.create_offer(name="D", priority=7),
+            factories.create_offer(name="E", priority=1),
+        ]
+
+        basket = factories.create_basket()
+        user = mock.Mock()
+
+        # Apply voucher to basket
+        basket.vouchers.add(voucher)
+
+        offers = utils.Applicator().get_basket_offers(basket, user)
+        ordered_names = [offer.name for offer in offers]
+        self.assertEqual(["B", "D", "C", "E", "A"], ordered_names)