forked from GamesDoneQuick/donation-tracker
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsearch_feeds.py
294 lines (248 loc) · 9.56 KB
/
search_feeds.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
from datetime import timedelta, datetime
import dateutil.parser
import pytz
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from tracker.models import Donation, SpeedRun, Bid
_DEFAULT_DONATION_DELTA = timedelta(hours=3)
_DEFAULT_DONATION_MAX = 200
_DEFAULT_DONATION_MIN = 25
# There is a slight complication in how this works, in that we cannot use the 'limit' set-up as a general filter mechanism, so these methods return the actual result, rather than a filter object
def get_recent_donations(
donations=None,
min_donations=_DEFAULT_DONATION_MIN,
max_donations=_DEFAULT_DONATION_MAX,
delta=_DEFAULT_DONATION_DELTA,
query_offset=None,
):
offset = default_time(query_offset)
if donations is None:
donations = Donation.objects.all()
if delta:
high_filter = donations.filter(timereceived__gte=offset - delta)
else:
high_filter = donations
count = high_filter.count()
if max_donations is not None and count > max_donations:
donations = donations[:max_donations]
elif min_donations is not None and count < min_donations:
donations = donations[:min_donations]
else:
donations = high_filter
return donations
_DEFAULT_RUN_DELTA = timedelta(hours=6)
_DEFAULT_RUN_MAX = 7
_DEFAULT_RUN_MIN = 3
def get_upcoming_runs(
runs=None,
include_current=True,
max_runs=_DEFAULT_RUN_MAX,
min_runs=_DEFAULT_RUN_MIN,
delta=_DEFAULT_RUN_DELTA,
query_offset=None,
):
offset = default_time(query_offset)
if runs is None:
runs = SpeedRun.objects.all()
if include_current:
runs = runs.filter(endtime__gte=offset)
else:
runs = runs.filter(starttime__gte=offset)
if delta:
high_filter = runs.filter(endtime__lte=offset + delta)
else:
high_filter = runs
count = high_filter.count()
if max_runs is not None and count > max_runs:
runs = runs[:max_runs]
elif min_runs is not None and count < min_runs:
runs = runs[:min_runs]
else:
runs = high_filter
return runs
def get_future_runs(**kwargs):
return get_upcoming_runs(include_current=False, **kwargs)
# TODO: why is this so complicated
def upcoming_bid_filter(**kwargs):
runs = [
run.id
for run in get_upcoming_runs(
SpeedRun.objects.filter(Q(bids__state='OPENED')).distinct(), **kwargs
)
]
return Q(speedrun__in=runs)
def get_upcoming_bids(**kwargs):
return Bid.objects.filter(upcoming_bid_filter(**kwargs))
def future_bid_filter(**kwargs):
return upcoming_bid_filter(include_current=False, **kwargs)
# Gets all of the current prizes that are possible right now (and also _specific_ to right now)
def concurrent_prizes_filter(runs):
run_count = runs.count()
if run_count == 0:
return Q(id=None)
start_time = runs[0].starttime
end_time = runs.reverse()[0].endtime
# TODO: with the other changes to the logic I'm not sure this is correct any more, but
# it's only a rough guess so maybe it's ok - BC 12/2019
# ----
# yes, the filter query here is correct. We want to get all unwon prizes that _start_ before the last run in the list _ends_, and likewise all prizes that _end_ after the first run in the list _starts_.
return Q(prizewinner__isnull=True) & (
Q(startrun__starttime__lte=end_time, endrun__endtime__gte=start_time)
| Q(starttime__lte=end_time, endtime__gte=start_time)
| Q(
startrun__isnull=True,
endrun__isnull=True,
starttime__isnull=True,
endtime__isnull=True,
)
)
def current_prizes_filter(query_offset=None):
offset = default_time(query_offset)
return Q(prizewinner__isnull=True) & (
Q(startrun__starttime__lte=offset, endrun__endtime__gte=offset)
| Q(starttime__lte=offset, endtime__gte=offset)
| Q(
startrun__isnull=True,
endrun__isnull=True,
starttime__isnull=True,
endtime__isnull=True,
)
)
def upcoming_prizes_filter(**kwargs):
runs = get_upcoming_runs(**kwargs)
return concurrent_prizes_filter(runs)
def future_prizes_filter(**kwargs):
return upcoming_prizes_filter(include_current=False, **kwargs)
def todraw_prizes_filter(query_offset=None):
offset = default_time(query_offset)
return Q(state='ACCEPTED') & (
Q(prizewinner__isnull=True)
& (
Q(endrun__endtime__lte=offset)
| Q(endtime__lte=offset)
| (Q(endtime=None) & Q(endrun=None))
)
)
def apply_feed_filter(query, model, feed_name, params=None, user=None):
params = params or {}
noslice = canonical_bool(params.pop('noslice', False))
user = user or AnonymousUser()
if model == 'donation':
query = donation_feed_filter(feed_name, noslice, params, query, user)
elif model in ['bid', 'bidtarget', 'allbids']:
query = bid_feed_filter(feed_name, noslice, params, query, user)
elif model == 'run':
query = run_feed_filter(feed_name, noslice, params, query)
elif model == 'prize':
query = prize_feed_filter(feed_name, noslice, params, query, user)
elif model == 'event':
query = event_feed_filter(feed_name, params, query)
return query
def event_feed_filter(feed_name, params, query):
if feed_name == 'future':
offsettime = default_time(params.get('time', None))
query = query.filter(datetime__gte=offsettime)
return query
def run_feed_filter(feed_name, noslice, params, query):
if feed_name == 'current':
query = get_upcoming_runs(**feed_params(noslice, params, {'runs': query}))
elif feed_name == 'future':
query = get_future_runs(**feed_params(noslice, params, {'runs': query}))
return query
def feed_params(noslice, params, init=None):
call_params = init or {}
if 'maxRuns' in params:
call_params['max_runs'] = int(params['maxRuns'])
if 'minRuns' in params:
call_params['min_runs'] = int(params['minRuns'])
if noslice:
call_params['max_runs'] = None
call_params['min_runs'] = None
if 'delta' in params:
call_params['delta'] = timedelta(minutes=int(params['delta']))
if 'time' in params:
call_params['query_offset'] = default_time(params['time'])
return call_params
def bid_feed_filter(feed_name, noslice, params, query, user):
if feed_name == 'all':
if not user.has_perm('tracker.view_hidden'):
raise PermissionDenied
pass # no filtering required
elif feed_name == 'open':
query = query.filter(state='OPENED')
elif feed_name == 'closed':
query = query.filter(state='CLOSED')
elif feed_name == 'current':
query = query.filter(state='OPENED').filter(
upcoming_bid_filter(**feed_params(noslice, params))
)
elif feed_name == 'future':
query = query.filter(state='OPENED').filter(
future_bid_filter(**feed_params(noslice, params))
)
elif feed_name == 'pending':
if not user.has_perm('tracker.view_hidden'):
raise PermissionDenied
query = query.filter(state='PENDING')
elif feed_name is None:
query = query.filter(state__in=['OPENED', 'CLOSED'])
else:
raise ValueError(f'Unknown feed name `{feed_name}`')
return query
def donation_feed_filter(feed_name, noslice, params, query, user):
if (
feed_name not in ['recent', 'toprocess', 'toread', 'all']
and feed_name is not None
):
raise ValueError(f'Unknown feed name `{feed_name}`')
if feed_name == 'recent':
query = get_recent_donations(
**feed_params(noslice, params, {'donations': query})
)
elif feed_name == 'toprocess':
if not user.has_perm('tracker.view_comments'):
raise PermissionDenied
query = query.filter((Q(commentstate='PENDING') | Q(readstate='PENDING')))
elif feed_name == 'toread':
query = query.filter(Q(readstate='READY'))
if feed_name != 'all':
query = query.filter(transactionstate='COMPLETED', testdonation=False)
elif not user.has_perm('tracker.view_pending'):
raise PermissionDenied
return query
def prize_feed_filter(feed_name, noslice, params, query, user):
if feed_name == 'current':
call_params = {}
if 'time' in params:
call_params['query_offset'] = default_time(params['time'])
query = query.filter(current_prizes_filter(**call_params))
elif feed_name == 'future':
query = query.filter(upcoming_prizes_filter(**feed_params(noslice, params)))
elif feed_name == 'won':
# TODO: are these used? doesn't seem to take multi-prizes into account
query = query.filter(Q(prizewinner__isnull=False))
elif feed_name == 'unwon':
query = query.filter(Q(prizewinner__isnull=True))
elif feed_name == 'todraw':
query = query.filter(todraw_prizes_filter())
if feed_name != 'all':
query = query.filter(state='ACCEPTED')
elif not user.has_perm('tracker.change_prize'):
raise PermissionDenied
return query
def canonical_bool(b):
if isinstance(b, str):
if b.lower() in ['t', 'True', 'true', 'y', 'yes']:
b = True
elif b.lower() in ['f', 'False', 'false', 'n', 'no']:
b = False
else:
b = None
return b
def default_time(time):
if time is None:
time = datetime.now(tz=pytz.utc)
elif isinstance(time, str):
time = dateutil.parser.parse(time)
return time.astimezone(pytz.utc)