fix race in filters
[wolnelektury.git] / src / messaging / states.py
1 from datetime import timedelta
2 from django.apps import apps
3 from django.utils.timezone import now
4 from django.utils.translation import gettext_lazy as _
5
6
7 class Level:
8     COLD = 10
9     TRIED = 20
10     SINGLE = 30
11     RECURRING = 40
12     MANUAL_MEMBER = 45
13     OPT_OUT = 50
14
15
16 class State:
17     allow_negative_offset = False
18     level = None
19     expired = None
20
21     def __init__(self, time=None, min_days_since=None, max_days_since=None, test=False):
22         self.time = time or now()
23         self.min_days_since = min_days_since
24         self.max_days_since = max_days_since
25         self.test = test
26
27     def get_contacts(self):
28         Contact = apps.get_model('messaging', 'Contact')
29         contacts = Contact.objects.filter(level=self.level)
30
31         if self.min_days_since is not None or self.expired:
32             cutoff = self.time - timedelta(self.min_days_since or 0)
33             if self.expired:
34                 contacts = contacts.filter(expires_at__lt=cutoff)
35             else:
36                 contacts = contacts.filter(since__lt=cutoff)
37
38         if self.max_days_since is not None:
39             cutoff = self.time - timedelta(self.max_days_since)
40             if self.expired:
41                 contacts = contacts.filter(expires_at__gt=cutoff)
42             else:
43                 contacts = contacts.filter(since__gt=cutoff)
44
45         if self.expired is False:
46             contacts = contacts.exclude(expires_at__lt=self.time)
47
48         return contacts
49
50     def get_context(self, contact):
51         if self.test:
52             return self.get_example_context(contact)
53
54         Schedule = apps.get_model('club', 'Schedule')
55         schedules = Schedule.objects.filter(email=contact.email)
56         return {
57             "schedule": self.get_schedule(schedules)
58         }
59
60     def get_example_context(self, contact):
61         Schedule = apps.get_model('club', 'Schedule')
62         return {
63             "schedule": Schedule(
64                 email=contact.email,
65                 key='xxxxxxxxx',
66                 amount=100,
67                 payed_at=self.time - timedelta(2),
68                 started_at=self.time - timedelta(1),
69                 expires_at=self.time + timedelta(1),
70             )
71         }
72
73
74 class ClubSingle(State):
75     slug = 'club-single'
76     name = _('club one-time donors')
77     level = Level.SINGLE
78     expired = False
79
80     def get_schedule(self, schedules):
81         # Find first single non-expired schedule.
82         return schedules.filter(
83             monthly=False, yearly=False,
84             expires_at__gt=self.time
85         ).order_by('started_at').first()
86
87
88 class ClubSingleExpired(State):
89     slug = 'club-membership-expiring'
90     allow_negative_offset = True
91     name = _('club one-time donors with donation expiring')
92     level = Level.SINGLE
93     expired = True
94
95     def get_schedule(self, schedules):
96         # Find last single expired schedule.
97         return schedules.filter(
98             monthly=False, yearly=False,
99             expires_at__lt=self.time
100         ).order_by('-expires_at').first()
101
102
103 class ClubTried(State):
104     slug = 'club-payment-unfinished'
105     name = _('club would-be donors')
106     level = Level.TRIED
107
108     def get_schedule(self, schedules):
109         # Find last unpaid schedule.
110         return schedules.filter(
111             payed_at=None
112         ).order_by('-started_at').first()
113
114
115 class ClubRecurring(State):
116     slug = 'club-recurring'
117     name = _('club recurring donors')
118     level = Level.RECURRING
119     expired = False
120
121     def get_schedule(self, schedules):
122         # Find first recurring non-expired schedule.
123         return schedules.exclude(
124             monthly=False, yearly=False
125         ).filter(
126             expires_at__gt=self.time
127         ).order_by('started_at').first()
128
129
130 class ClubRecurringExpired(State):
131     slug = 'club-recurring-payment-problem'
132     name = _('club recurring donors with donation expired')
133     level = Level.RECURRING
134     expired = True
135
136     def get_schedule(self, schedules):
137         # Find last recurring expired schedule.
138         return schedules.exclude(
139             monthly=False, yearly=False
140         ).filter(
141             expires_at__lt=self.time
142         ).order_by('-expires_at').first()
143
144
145 class Cold(State):
146     slug = 'cold'
147     name = _('cold group')
148     level = Level.COLD
149
150     def get_context(self, contact):
151         return {}
152
153
154 states = [
155     Cold,
156     ClubTried,
157     ClubSingle,
158     ClubSingleExpired,
159     ClubRecurring,
160     ClubRecurringExpired,
161 ]
162