Explorar el Código

Fix logic for checking whether an offer is active based on date.

- If start_datetime is null, then the offer has no start date.
- If end_datetime is null, then the offer never expires.

Use the ActiveOfferManager in Applicator.get_site_offers instead of
reimplementing the same logic there.

Fixes #2344.
master
Samir Shah hace 8 años
padre
commit
1784dfef42

+ 5
- 2
src/oscar/apps/offer/abstract_models.py Ver fichero

@@ -89,10 +89,13 @@ class AbstractConditionalOffer(models.Model):
89 89
     # dates are ignored and only the dates from the voucher are used to
90 90
     # determine availability.
91 91
     start_datetime = models.DateTimeField(
92
-        _("Start date"), blank=True, null=True)
92
+        _("Start date"), blank=True, null=True,
93
+        help_text=_("Offers are active from the start date. "
94
+                    "Leave this empty if the offer has no start date."))
93 95
     end_datetime = models.DateTimeField(
94 96
         _("End date"), blank=True, null=True,
95
-        help_text=_("Offers are active until the end of the 'end date'"))
97
+        help_text=_("Offers are active until the end date. "
98
+                    "Leave this empty if the offer has no expiry date."))
96 99
 
97 100
     # Use this field to limit the number of times this offer can be applied in
98 101
     # total.  Note that a single order can apply an offer multiple times so

+ 1
- 12
src/oscar/apps/offer/applicator.py Ver fichero

@@ -67,19 +67,8 @@ class Applicator(object):
67 67
         """
68 68
         Return site offers that are available to all users
69 69
         """
70
-        cutoff = now()
71
-        date_based = Q(
72
-            Q(start_datetime__lte=cutoff),
73
-            Q(end_datetime__gte=cutoff) | Q(end_datetime=None),
74
-        )
75
-
76
-        nondate_based = Q(start_datetime=None, end_datetime=None)
77
-
78 70
         ConditionalOffer = get_model('offer', 'ConditionalOffer')
79
-        qs = ConditionalOffer.objects.filter(
80
-            date_based | nondate_based,
81
-            offer_type=ConditionalOffer.SITE,
82
-            status=ConditionalOffer.OPEN)
71
+        qs = ConditionalOffer.active.filter(offer_type=ConditionalOffer.SITE)
83 72
         # Using select_related with the condition/benefit ranges doesn't seem
84 73
         # to work.  I think this is because both the related objects have the
85 74
         # FK to range with the same name.

+ 2
- 1
src/oscar/apps/offer/managers.py Ver fichero

@@ -10,7 +10,8 @@ class ActiveOfferManager(models.Manager):
10 10
         cutoff = now()
11 11
         return super(ActiveOfferManager, self).get_queryset().filter(
12 12
             models.Q(end_datetime__gte=cutoff) | models.Q(end_datetime=None),
13
-            start_datetime__lte=cutoff).filter(status=self.model.OPEN)
13
+            models.Q(start_datetime__lte=cutoff) | models.Q(start_datetime=None),
14
+        ).filter(status=self.model.OPEN)
14 15
 
15 16
 
16 17
 class BrowsableRangeManager(models.Manager):

+ 25
- 0
src/oscar/apps/offer/migrations/0006_auto_20170504_0616.py Ver fichero

@@ -0,0 +1,25 @@
1
+# -*- coding: utf-8 -*-
2
+# Generated by Django 1.11 on 2017-05-04 05:16
3
+from __future__ import unicode_literals
4
+
5
+from django.db import migrations, models
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('offer', '0005_auto_20170423_1217'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AlterField(
16
+            model_name='conditionaloffer',
17
+            name='end_datetime',
18
+            field=models.DateTimeField(blank=True, help_text='Offers are active until the end date. Leave this empty if the offer has no expiry date.', null=True, verbose_name='End date'),
19
+        ),
20
+        migrations.AlterField(
21
+            model_name='conditionaloffer',
22
+            name='start_datetime',
23
+            field=models.DateTimeField(blank=True, help_text='Offers are active from the start date. Leave this empty if the offer has no start date.', null=True, verbose_name='Start date'),
24
+        ),
25
+    ]

+ 13
- 0
tests/integration/offer/test_applicator.py Ver fichero

@@ -58,6 +58,19 @@ class TestOfferApplicator(TestCase):
58 58
         priorities = [offer.priority for offer in offers]
59 59
         self.assertEqual(sorted(priorities, reverse=True), priorities)
60 60
 
61
+    def test_get_site_offers(self):
62
+        site_offer = models.ConditionalOffer.objects.create(
63
+            name="globaloffer", condition=self.condition,
64
+            benefit=self.benefit, offer_type=models.ConditionalOffer.SITE)
65
+        session_offer = models.ConditionalOffer.objects.create(
66
+            name="sessionoffer", condition=self.condition,
67
+            benefit=self.benefit, offer_type=models.ConditionalOffer.SESSION)
68
+
69
+        site_offers = Applicator().get_site_offers()
70
+        # Only one offer should be returned
71
+        self.assertEqual(len(site_offers), 1)
72
+        self.assertEqual(site_offers[0].name, "globaloffer")
73
+
61 74
 
62 75
 class TestOfferApplicationsWrapper(TestCase):
63 76
 

+ 20
- 0
tests/integration/offer/test_availability.py Ver fichero

@@ -59,6 +59,26 @@ class TestADateBasedConditionalOffer(TestCase):
59 59
     def test_is_active_on_end_datetime(self):
60 60
         self.assertTrue(self.offer.is_available(test_date=self.end))
61 61
 
62
+    def test_active_on_null_end_datetime(self):
63
+        # null end_datetime means offer should never expire
64
+        offer = models.ConditionalOffer(start_datetime=self.start,
65
+                                        end_datetime=None)
66
+        test = datetime.date(2017, 3, 10)
67
+        self.assertTrue(offer.is_available(test_date=test))
68
+
69
+    def test_active_on_null_start_datetime(self):
70
+        # null start_datetime means offer is active from the beginning of time
71
+        offer = models.ConditionalOffer(start_datetime=None,
72
+                                        end_datetime=self.end)
73
+        test = datetime.date(2000, 3, 10)
74
+        self.assertTrue(offer.is_available(test_date=test))
75
+
76
+    def test_active_on_null_start_and_end_datetime(self):
77
+        # null datetimes - offer is always available
78
+        offer = models.ConditionalOffer(start_datetime=None, end_datetime=None)
79
+        test = datetime.date(2017, 3, 10)
80
+        self.assertTrue(offer.is_available(test_date=test))
81
+
62 82
 
63 83
 class TestAConsumptionFrequencyBasedConditionalOffer(TestCase):
64 84
 

+ 19
- 1
tests/integration/offer/test_manager.py Ver fichero

@@ -10,7 +10,6 @@ from oscar.apps.offer import models
10 10
 class TestActiveOfferManager(TestCase):
11 11
 
12 12
     def test_includes_offers_in_date_range(self):
13
-        # Create offer that is available but with the wrong status
14 13
         now = timezone.now()
15 14
         start = now - datetime.timedelta(days=1)
16 15
         end = now + datetime.timedelta(days=1)
@@ -19,6 +18,25 @@ class TestActiveOfferManager(TestCase):
19 18
         filtered_offers = models.ConditionalOffer.active.all()
20 19
         self.assertEqual(1, len(filtered_offers))
21 20
 
21
+    def test_includes_offers_with_null_start_date(self):
22
+        now = timezone.now()
23
+        end = now + datetime.timedelta(days=1)
24
+        factories.create_offer(start=None, end=end)
25
+        filtered_offers = models.ConditionalOffer.active.all()
26
+        self.assertEqual(1, len(filtered_offers))
27
+
28
+    def test_includes_offers_with_null_end_date(self):
29
+        now = timezone.now()
30
+        start = now - datetime.timedelta(days=1)
31
+        factories.create_offer(start=start, end=None)
32
+        filtered_offers = models.ConditionalOffer.active.all()
33
+        self.assertEqual(1, len(filtered_offers))
34
+
35
+    def test_includes_offers_with_null_start_and_end_date(self):
36
+        factories.create_offer(start=None, end=None)
37
+        filtered_offers = models.ConditionalOffer.active.all()
38
+        self.assertEqual(1, len(filtered_offers))
39
+
22 40
     def test_filters_out_expired_offers(self):
23 41
         # Create offer that is available but with the wrong status
24 42
         now = timezone.now()

Loading…
Cancelar
Guardar