-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmodels.py
292 lines (245 loc) · 11.2 KB
/
models.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
## Copyright (c) 2010 by Jose Antonio Martin <jantonio.martin AT gmail DOT com>
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU Affero General Public License as published by the
## Free Software Foundation, either version 3 of the License, or (at your option
## any later version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
## for more details.
##
## You should have received a copy of the GNU Affero General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/agpl.txt>.
##
## This license is also included in the file COPYING
##
## AUTHOR: Jose Antonio Martin <jantonio.martin AT gmail DOT com>
from datetime import datetime
import pytz
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.conf import global_settings
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.urls import reverse
from transmeta import TransMeta
from avatar.models import Avatar
from avatar.templatetags import avatar_tags
#from condottieri_profiles.defaults import *
from machiavelli.signals import government_overthrown, player_joined, player_surrendered
if "pinax.notifications" in settings.INSTALLED_APPS:
from pinax.notifications import models as notification
else:
notification = None
if 'pybb' in settings.INSTALLED_APPS:
import pybb.defaults
SIGNATURE_MAX_LENGTH = pybb.defaults.PYBB_SIGNATURE_MAX_LENGTH
DEFAULT_AUTOSUBSCRIBE = pybb.defaults.PYBB_DEFAULT_AUTOSUBSCRIBE
else:
SIGNATURE_MAX_LENGTH = settings.SIGNATURE_MAX_LENGTH
DEFAULT_AUTOSUBSCRIBE = settings.DEFAULT_AUTOSUBSCRIBE
class CondottieriProfileManager(models.Manager):
def hall_of_fame(self, order='weighted_score'):
fields = [f.name for f in CondottieriProfile._meta.get_fields()]
if not (order in fields \
or order in ['avg_score', 'avg_victories']):
order = 'weighted_score'
order = ''.join(['-', order])
return self.filter(total_score__gt=0, finished_games__gt=2).extra(
select={'avg_victories': "100 * (victories / finished_games)",
'avg_score': "total_score / finished_games"}).order_by(order)
class CondottieriProfile(models.Model):
""" Defines the actual profile for a Condottieri user.
"""
user = models.OneToOneField(User, verbose_name=_('user'), related_name='profile',
on_delete=models.CASCADE)
""" A User object related to the profile """
name = models.CharField(_('name'), max_length=50, null=True, blank=True)
""" The user complete name """
about = models.TextField(_('about'), null=True, blank=True)
""" More user info """
location = models.CharField(_('location'), max_length=40, null=True, blank=True)
""" Geographic location string """
website = models.URLField(_("website"), null = True, blank = True)
karma = models.PositiveIntegerField(default=settings.KARMA_DEFAULT, editable=False)
""" Total karma value """
total_score = models.IntegerField(default=0, editable=False)
""" Sum of game scores """
weighted_score = models.IntegerField(default=0, editable=False)
""" Sum of devaluated game scores """
finished_games = models.PositiveIntegerField(default=0, editable=False)
""" Number of games that the player has played to the end """
victories = models.PositiveIntegerField(default=0, editable=False)
""" Number of victories """
overthrows = models.PositiveIntegerField(default=0, editable=False)
""" Number of times that the player has been overthrown """
surrenders = models.PositiveIntegerField(default=0, editable=False)
""" Number of times that the player has surrendered """
badges = models.ManyToManyField('Badge', verbose_name=_("badges"))
is_editor = models.BooleanField(_("Is editor?"), default=False)
""" Fields needed by pybbm """
signature = models.TextField(_("Signature"), blank=True,
max_length=SIGNATURE_MAX_LENGTH)
signature_html = models.TextField(_("Signature HTML Version"), blank=True,
max_length=SIGNATURE_MAX_LENGTH + 30)
show_signatures = models.BooleanField(_("Show signatures"), blank=True,
default=True)
post_count = models.IntegerField(_('Post count'), blank=True, default=0)
autosubscribe = models.BooleanField(_("Automatically subscribe"),
help_text=_("Automatically subscribe to topics that you answer"),
default=DEFAULT_AUTOSUBSCRIBE)
objects = CondottieriProfileManager()
def save(self, *args, **kwargs):
if 'pybb' in settings.INSTALLED_APPS:
from pybb.util import get_markup_engine
markup = get_markup_engine()
self.signature_html = markup.format(self.signature)
else:
self.signature_html = self.signature
super(CondottieriProfile, self).save(*args, **kwargs)
def __str__(self):
return self.user.username
def get_absolute_url(self):
return reverse('profile_detail', kwargs={'username': self.user.username})
def has_languages(self):
""" Returns true if the user has defined at least one known language """
try:
SpokenLanguage.objects.get(profile=self)
except MultipleObjectsReturned:
return True
except ObjectDoesNotExist:
return False
else:
return True
def average_score(self):
if self.finished_games > 0:
return float(self.total_score) / self.finished_games
else:
return 0
def adjust_karma(self, k):
""" Adds or substracts some karma to the total """
if not isinstance(k, int):
return
new_karma = self.karma + k
if new_karma > settings.KARMA_MAXIMUM:
new_karma = settings.KARMA_MAXIMUM
elif new_karma < settings.KARMA_MINIMUM:
new_karma = settings.KARMA_MINIMUM
self.karma = new_karma
self.save()
def overthrow(self):
""" Add 1 to the overthrows counter of the profile """
self.overthrows += 1
self.save()
def check_karma_to_join(self, fast=False, private=False):
if self.karma < settings.KARMA_TO_JOIN:
return _("You need a minimum karma of %s to play a game.") % \
settings.KARMA_TO_JOIN
if fast and self.karma < settings.KARMA_TO_FAST:
return _("You need a minimum karma of %s to play a fast game.") % \
settings.KARMA_TO_FAST
if private and self.karma < settings.KARMA_TO_PRIVATE:
return \
_("You need a minimum karma of %s to create a private game.") \
% settings.KARMA_TO_PRIVATE
if self.karma < settings.KARMA_TO_UNLIMITED:
current_games = self.user.player_set.all().count()
if current_games >= settings.GAMES_LIMIT:
return _("You need karma %s to play more than %s games.") % \
(settings.KARMA_TO_UNLIMITED, settings.GAMES_LIMIT)
return ""
##
## Properties to proxy fields in PybbProfile
##
def _get_time_zone(self):
t = datetime.today()
today = datetime(t.year, t.month, t.day)
try:
tz = pytz.timezone(self.user.account.timezone)
except:
return 0
offset = float(tz.utcoffset(today).seconds) / 3600
return offset
time_zone = property(_get_time_zone)
def _get_language(self):
return self.user.account.language
language = property(_get_language)
def get_display_name(self):
return self.user.username
def add_overthrow(sender, **kwargs):
if not sender.voluntary:
profile = sender.government.profile
profile.overthrow()
government_overthrown.connect(add_overthrow)
def add_surrender(sender, **kwargs):
profile = sender.user.profile
profile.surrenders += 1
profile.adjust_karma(settings.SURRENDER_KARMA)
player_surrendered.connect(add_surrender)
def create_profile(sender, instance, created, raw, **kwargs):
""" Creates a profile associated to a User """
if raw:
return
if instance is None:
return
##The following line prevents pybb causing an IntegrityError when it tries
##to create the user profile for a second time.
##TODO: Look for a better solution.
if 'pybb' in settings.INSTALLED_APPS:
return
profile, created = CondottieriProfile.objects.get_or_create(user=instance)
post_save.connect(create_profile, sender=User)
class SpokenLanguage(models.Model):
""" Defines a language that a User understands """
code = models.CharField(_("language"), max_length=8, choices=global_settings.LANGUAGES)
profile = models.ForeignKey(CondottieriProfile, on_delete=models.CASCADE)
def __str__(self):
return self.get_code_display()
class Meta:
unique_together = (('code', 'profile',),)
class Friendship(models.Model):
"""
Defines a one-way friendship relationship between two users.
"""
friend_from = models.ForeignKey(User, related_name="friends", on_delete=models.CASCADE)
friend_to = models.ForeignKey(User, related_name="friend_of", on_delete=models.CASCADE)
created_on = models.DateTimeField(editable=False, auto_now_add=True)
class Meta:
unique_together = (('friend_from', 'friend_to',),)
def __str__(self):
return "%s is a friend of %s" % (self.friend_to, self.friend_from)
def was_befriended(sender, instance, created, raw, **kwargs):
""" Notify a user when other user befriends him """
if notification and created and not raw:
recipients = [instance.friend_to, ]
extra_context = {'username': instance.friend_from,
'STATIC_URL': settings.STATIC_URL,}
notification.send(recipients, "new_friend", extra_context)
post_save.connect(was_befriended, sender=Friendship)
def friend_joined_game(sender, **kwargs):
""" Notify a user if a friend joins a game """
if notification:
user = sender.user
friend_of_ids = user.friend_of.values_list('friend_from', flat=True)
recipients = []
for f in user.friends.all():
if f.friend_to.id in friend_of_ids:
recipients.append(f.friend_to)
extra_context = {'username': sender.user.username,
'slug': sender.game.slug,
'STATIC_URL': settings.STATIC_URL,}
notification.send(recipients, "friend_joined", extra_context)
player_joined.connect(friend_joined_game)
class Badge(models.Model, metaclass=TransMeta):
""" Defines an award or honor that a user may earn """
image = models.ImageField(_("image"), upload_to="badges")
description = models.CharField(_("description"), max_length=200)
class Meta:
verbose_name = _("badge")
verbose_name_plural = _("badges")
translate = ('description',)
def __str__(self):
return "%s" % self.description