-
Notifications
You must be signed in to change notification settings - Fork 430
/
Copy pathtrophies.py
286 lines (220 loc) · 8.57 KB
/
trophies.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
import re
from django.utils.translation import gettext_lazy as _
from djblets.registries.registry import (ALREADY_REGISTERED,
ATTRIBUTE_REGISTERED,
DEFAULT_ERRORS,
NOT_REGISTERED,
Registry,
UNREGISTER)
from djblets.urls.staticfiles import static_lazy
from djblets.util.decorators import augment_method_from
class TrophyType(object):
"""Base class for a type of trophy.
Trophies are achievements that can be awarded to users based on some
aspect of a review request. When a review request is filed, each registered
trophy type (managed by the :py:data:`trophies` registry) will be checked
using :py:meth:`qualifies` to see if the trophy can be awarded. If so, the
trophy will be recorded and shown on the review request page.
A trophy should include a displayable name, a category (essentially the
ID of the trophy), and details for the trophy image.
"""
#: The category of the trophy.
#:
#: This is the string ID of the trophy. For historical reasons, it's
#: referred to as a category and not an ID.
category = None
#: The name of the trophy.
name = None
#: URLs for the trophy images.
#:
#: This is a dictionary of images, where each key is a resolution
#: specifier (``1x``, ``2x``, etc.), and the value is a URL.
#:
#: Each must have widths/heights that are multipliers on the base
#: width/height for the ``1x`` specifier.
image_urls = {}
#: The width of the base image.
image_width = None
#: The height of the base image.
#:
#: It is recommended to use a height of 48px max.
image_height = None
def get_display_text(self, trophy):
"""Return the text to display in the trophy banner.
Args:
trophy (reviewboard.accounts.models.Trophy):
The stored trophy information.
Returns:
unicode:
The display text for the trophy banner.
"""
raise NotImplementedError
def qualifies(self, review_request):
"""Return whether this trophy should be given to this review request.
Args:
review_request (reviewboard.reviews.models.ReviewRequest):
The review request to check for the trophy.
Returns:
bool:
``True`` if the trophy should be given, or ``False`` if not.
"""
raise NotImplementedError
def format_display_text(self, request, trophy, **kwargs):
"""Format the display text for the trophy.
Args:
request (django.http.HttpRequest):
The HTTP request from the client.
trophy (reviewboard.accounts.models.Trophy):
The trophy instance.
**kwargs (dict):
Additional keyword arguments to use for formatting.
Returns:
unicode:
The rendered text.
"""
if self.display_format_str is None:
raise NotImplementedError(
'%s does not define the format_display_str attribute.'
% type(self).__name__
)
return self.display_format_str % dict(kwargs, **{
'recipient': trophy.user.get_profile().get_display_name(
getattr(request, 'user', None)),
'review_request_id': trophy.review_request.display_id,
})
class MilestoneTrophy(TrophyType):
"""A milestone trophy.
It is awarded if review request ID is greater than 1000 and is a non-zero
digit followed by only zeroes (e.g. 1000, 5000, 10000).
"""
category = 'milestone'
title = _('Milestone Trophy')
image_urls = {
'1x': static_lazy('rb/images/trophies/sparkly.png'),
'2x': static_lazy('rb/images/trophies/sparkly@2x.png'),
}
image_width = 33
image_height = 35
display_format_str = _(
'%(recipient)s got review request #%(review_request_id)d!'
)
def qualifies(self, review_request):
"""Return whether this trophy should be given to this review request.
Args:
review_request (reviewboard.reviews.models.ReviewRequest):
The review request to check for the trophy.
Returns:
bool:
``True`` if the trophy should be given, or ``False`` if not.
"""
return (
review_request.display_id >= 1000 and
re.match(r'^[1-9]0+$', str(review_request.display_id))
)
class FishTrophy(TrophyType):
"""A fish trophy.
Give a man a fish, he'll waste hours trying to figure out why.
"""
category = 'fish'
name = _('Fish Trophy')
image_urls = {
'1x': static_lazy('rb/images/trophies/fish.png'),
'2x': static_lazy('rb/images/trophies/fish@2x.png'),
}
image_width = 33
image_height = 37
display_format_str = _('%(recipient)s got a fish trophy!')
def qualifies(self, review_request):
"""Return whether this trophy should be given to this review request.
Args:
review_request (reviewboard.reviews.models.ReviewRequest):
The review request to check for the trophy.
Returns:
bool:
``True`` if the trophy should be given, or ``False`` if not.
"""
id_str = str(review_request.display_id)
return (review_request.display_id >= 1000 and
id_str == ''.join(reversed(id_str)))
class UnknownTrophy(TrophyType):
"""A trophy with an unknown category.
The data for this trophy exists in the database but its category does not
match the category of any registered trophy types.
"""
name = 'Unknown Trophy'
class TrophyRegistry(Registry):
lookup_attrs = ('category',)
default_errors = dict(DEFAULT_ERRORS, **{
ALREADY_REGISTERED: _(
'Could not register trophy type %(item)s. This trophy type is '
'already registered or its category conflicts with another trophy.'
),
ATTRIBUTE_REGISTERED: _(
'Could not register trophy type %(item)s: Another trophy type '
'(%(duplicate)s) is already registered with the same category.'
),
NOT_REGISTERED: _(
'No trophy type was found matching "%(attr_value)s".'
),
UNREGISTER: _(
'Could not unregister trophy type %(item)s: This trophy type '
'was not yet registered.'
),
})
@augment_method_from(Registry)
def register(self, trophy_type):
"""Register a new trophy type.
Args:
trophy_type (type):
The trophy type (subclass of :py:class:`TrophyType`) to
register.
Raises:
djblets.registries.errors.RegistrationError:
The :py:attr:`TrophyType.category` value is missing on the
trophy.
djblets.registries.errors.AlreadyRegisteredError:
This trophy type, or another with the same category, was
already registered.
"""
pass
@augment_method_from(Registry)
def unregister(self, trophy_type):
"""Unregister a trophy type.
Args:
trophy_type (type):
The trophy type (subclass of :py:class:`TrophyType`) to
unregister.
Raises:
djblets.registries.errors.ItemLookupError:
This trophy type was not registered.
"""
pass
def get_for_category(self, category):
"""Return the TrophyType instance matching a given trophy category.
If there's no registered trophy for the category,
:py:class:`UnknownTrophy` will be returned.
Args:
category (unicode):
The stored category for the trophy.
Returns:
TrophyType:
The trophy matching the given category.
"""
try:
return self.get('category', category)
except self.lookup_error_class:
return UnknownTrophy
def get_defaults(self):
"""Return the default trophies for the registry.
This is used internally by the parent registry class to populate the
list of default, built-in trophies available to review requests.
Returns:
list of TrophyType:
The list of default trophies.
"""
return [
MilestoneTrophy,
FishTrophy,
]
#: The registry of available trophies.
trophies_registry = TrophyRegistry()