gremmie@1
|
1 """
|
gremmie@1
|
2 Contains models for the bio application.
|
gremmie@1
|
3 I would have picked profile for this application, but that is already taken, apparently.
|
gremmie@1
|
4 """
|
bgneal@277
|
5 import datetime
|
gremmie@1
|
6 import os.path
|
gremmie@1
|
7
|
gremmie@1
|
8 from django.db import models
|
bgneal@259
|
9 from django.contrib.auth.models import User
|
gremmie@1
|
10 from django.conf import settings
|
bgneal@54
|
11 from django.core.cache import cache
|
bgneal@1035
|
12 from django.core.urlresolvers import reverse
|
bgneal@277
|
13 from django.template.loader import render_to_string
|
gremmie@1
|
14
|
bgneal@128
|
15 from core.markup import SiteMarkup
|
bgneal@609
|
16 import bio.flags
|
bgneal@919
|
17 from bio.signals import notify_profile_content_update
|
bgneal@124
|
18
|
bgneal@562
|
19
|
bgneal@215
|
20 # These are the secondary user status enumeration values.
|
bgneal@215
|
21 (STA_ACTIVE, # User is a full member in good standing.
|
bgneal@215
|
22 STA_RESIGNED, # User has voluntarily asked to be removed.
|
bgneal@215
|
23 STA_REMOVED, # User was removed for bad behavior.
|
bgneal@215
|
24 STA_SUSPENDED, # User is temporarily suspended; e.g. a stranger tripped
|
bgneal@215
|
25 # the spam filter.
|
bgneal@215
|
26 STA_SPAMMER, # User has been removed for spamming.
|
bgneal@215
|
27 STA_STRANGER, # New member, isn't fully trusted yet. Their comments and
|
bgneal@215
|
28 # forum posts are scanned for spam. They can have their
|
bgneal@215
|
29 # accounts deactivated by moderators for spamming.
|
bgneal@215
|
30 ) = range(6)
|
bgneal@147
|
31
|
bgneal@147
|
32 USER_STATUS_CHOICES = (
|
bgneal@147
|
33 (STA_ACTIVE, "Active"),
|
bgneal@147
|
34 (STA_RESIGNED, "Resigned"),
|
bgneal@147
|
35 (STA_REMOVED, "Removed"),
|
bgneal@147
|
36 (STA_SUSPENDED, "Suspended"),
|
bgneal@147
|
37 (STA_SPAMMER, "Spammer"),
|
bgneal@215
|
38 (STA_STRANGER, "Stranger")
|
bgneal@147
|
39 )
|
bgneal@147
|
40
|
bgneal@204
|
41
|
bgneal@204
|
42 class Badge(models.Model):
|
bgneal@204
|
43 """This model represents badges that users can earn."""
|
bgneal@204
|
44 image = models.ImageField(upload_to='badges')
|
bgneal@204
|
45 name = models.CharField(max_length=64)
|
bgneal@204
|
46 description = models.TextField(blank=True)
|
bgneal@204
|
47 order = models.IntegerField()
|
bgneal@204
|
48 numeric_id = models.IntegerField(db_index=True)
|
bgneal@204
|
49
|
bgneal@204
|
50 class Meta:
|
bgneal@204
|
51 ordering = ('order', )
|
bgneal@204
|
52
|
bgneal@204
|
53 def __unicode__(self):
|
bgneal@204
|
54 return self.name
|
bgneal@204
|
55
|
bgneal@204
|
56 def get_absolute_url(self):
|
bgneal@204
|
57 return self.image.url
|
bgneal@204
|
58
|
bgneal@204
|
59 def html(self):
|
bgneal@204
|
60 """Returns a HTML img tag representation of the badge."""
|
bgneal@204
|
61 if self.image:
|
bgneal@204
|
62 return u'<img src="%s" alt="%s" title="%s" />' % (
|
bgneal@204
|
63 self.get_absolute_url(), self.name, self.name)
|
bgneal@204
|
64 return u''
|
bgneal@204
|
65 html.allow_tags = True
|
bgneal@204
|
66
|
bgneal@204
|
67
|
gremmie@1
|
68 def avatar_file_path(instance, filename):
|
bgneal@666
|
69 # The avatar's name is the username plus the current time plus the file
|
bgneal@666
|
70 # extension. The time is used to bust browser caches.
|
bgneal@666
|
71 # Get extension, if it exists:
|
bgneal@560
|
72 ext = os.path.splitext(filename)[1]
|
bgneal@560
|
73 if not ext:
|
bgneal@560
|
74 ext = '.jpg'
|
bgneal@666
|
75 avatar_name = "{}{}{}".format(instance.user.username,
|
bgneal@666
|
76 datetime.datetime.utcnow().strftime('%S%f'), ext)
|
bgneal@560
|
77 return os.path.join(settings.AVATAR_DIR, 'users', avatar_name)
|
gremmie@1
|
78
|
gremmie@1
|
79
|
gremmie@1
|
80 class UserProfile(models.Model):
|
gremmie@1
|
81 """model to represent additional information about users"""
|
gremmie@1
|
82
|
bgneal@1206
|
83 user = models.OneToOneField(User, related_name='profile',
|
bgneal@1206
|
84 on_delete=models.CASCADE)
|
gremmie@1
|
85 location = models.CharField(max_length=128, blank=True)
|
bgneal@609
|
86 country = models.CharField(max_length=2, blank=True, default='',
|
bgneal@609
|
87 choices=bio.flags.FLAG_CHOICES,
|
bgneal@609
|
88 help_text='Optional')
|
gremmie@1
|
89 birthday = models.DateField(blank=True, null=True,
|
gremmie@1
|
90 help_text='Optional; the year is not shown to others')
|
gremmie@1
|
91 occupation = models.CharField(max_length=128, blank=True)
|
gremmie@1
|
92 interests = models.CharField(max_length=255, blank=True)
|
gremmie@1
|
93 profile_text = models.TextField(blank=True)
|
gremmie@1
|
94 profile_html = models.TextField(blank=True)
|
gremmie@1
|
95 hide_email = models.BooleanField(default=True)
|
gremmie@1
|
96 signature = models.TextField(blank=True)
|
gremmie@1
|
97 signature_html = models.TextField(blank=True)
|
gremmie@1
|
98 avatar = models.ImageField(upload_to=avatar_file_path, blank=True)
|
bgneal@70
|
99 time_zone = models.CharField(max_length=64, blank=True,
|
bgneal@70
|
100 default='US/Pacific')
|
bgneal@120
|
101 use_24_time = models.BooleanField(default=False)
|
bgneal@96
|
102 forum_post_count = models.IntegerField(default=0)
|
bgneal@215
|
103 status = models.IntegerField(default=STA_STRANGER,
|
bgneal@147
|
104 choices=USER_STATUS_CHOICES)
|
bgneal@147
|
105 status_date = models.DateTimeField(auto_now_add=True)
|
bgneal@204
|
106 badges = models.ManyToManyField(Badge, through="BadgeOwnership")
|
bgneal@277
|
107 update_date = models.DateTimeField(db_index=True, blank=True)
|
bgneal@390
|
108 auto_favorite = models.BooleanField(default=False)
|
bgneal@390
|
109 auto_subscribe = models.BooleanField(default=False)
|
gremmie@1
|
110
|
gremmie@1
|
111 def __unicode__(self):
|
gremmie@1
|
112 return self.user.username
|
gremmie@1
|
113
|
gremmie@1
|
114 class Meta:
|
gremmie@1
|
115 ordering = ('user__username', )
|
gremmie@1
|
116
|
gremmie@1
|
117 def save(self, *args, **kwargs):
|
bgneal@562
|
118 """
|
bgneal@562
|
119 Custom profile save() function.
|
bgneal@562
|
120 If content_update is True (default), then it is assumed that major
|
bgneal@562
|
121 fields are being updated and that the profile_content_update signal
|
bgneal@562
|
122 should be signalled. When content_update is False, the update_date is
|
bgneal@562
|
123 not updated, expensive markup conversions are not performed, and the
|
bgneal@562
|
124 signal is not signalled. This is useful for updating the
|
bgneal@562
|
125 forum_post_count, for example.
|
bgneal@562
|
126
|
bgneal@562
|
127 """
|
bgneal@562
|
128 content_update = kwargs.pop('content_update', True)
|
bgneal@562
|
129
|
bgneal@562
|
130 if content_update:
|
bgneal@562
|
131 self.update_date = datetime.datetime.now()
|
bgneal@562
|
132 sm = SiteMarkup()
|
bgneal@562
|
133 self.profile_html = sm.convert(self.profile_text)
|
bgneal@562
|
134 self.signature_html = sm.convert(self.signature)
|
bgneal@562
|
135 cache.delete('avatar_' + self.user.username)
|
bgneal@562
|
136
|
gremmie@1
|
137 super(UserProfile, self).save(*args, **kwargs)
|
bgneal@562
|
138
|
bgneal@562
|
139 if content_update:
|
bgneal@562
|
140 notify_profile_content_update(self)
|
bgneal@138
|
141
|
bgneal@138
|
142 def get_absolute_url(self):
|
bgneal@1035
|
143 return reverse('bio-view_profile', kwargs={'username': self.user.username})
|
bgneal@138
|
144
|
bgneal@204
|
145 def badge_ownership(self):
|
bgneal@329
|
146 return BadgeOwnership.objects.filter(profile=self).select_related('badge')
|
bgneal@204
|
147
|
bgneal@215
|
148 def is_stranger(self):
|
bgneal@215
|
149 """Returns True if this user profile status is STA_STRANGER."""
|
bgneal@215
|
150 return self.status == STA_STRANGER
|
bgneal@215
|
151
|
bgneal@215
|
152 def user_is_active(self):
|
bgneal@215
|
153 """Returns the profile's user is_active status. This function exists
|
bgneal@215
|
154 for the admin.
|
bgneal@215
|
155 """
|
bgneal@215
|
156 return self.user.is_active
|
bgneal@215
|
157 user_is_active.boolean = True
|
bgneal@215
|
158 user_is_active.short_description = "Is Active"
|
bgneal@215
|
159
|
bgneal@625
|
160 def reset(self):
|
bgneal@363
|
161 """
|
bgneal@625
|
162 Reset profile fields to empty defaults.
|
bgneal@363
|
163 This function is useful when a spammer is identified.
|
bgneal@363
|
164
|
bgneal@363
|
165 """
|
bgneal@363
|
166 self.location = ''
|
bgneal@625
|
167 self.country = ''
|
bgneal@625
|
168 self.birthday = None
|
bgneal@363
|
169 self.occupation = ''
|
bgneal@363
|
170 self.interests = ''
|
bgneal@363
|
171 self.profile_text = ''
|
bgneal@363
|
172 self.signature = ''
|
bgneal@363
|
173
|
bgneal@223
|
174 def search_title(self):
|
bgneal@223
|
175 full_name = self.user.get_full_name()
|
bgneal@223
|
176 if full_name:
|
bgneal@223
|
177 return u"%s (%s)" % (self.user.username, full_name)
|
bgneal@223
|
178 return self.user.username
|
bgneal@223
|
179
|
bgneal@223
|
180 def search_summary(self):
|
bgneal@277
|
181 text = render_to_string('search/indexes/bio/userprofile_text.txt',
|
bgneal@277
|
182 {'object': self});
|
bgneal@277
|
183 return text
|
bgneal@223
|
184
|
bgneal@138
|
185
|
bgneal@138
|
186 class UserProfileFlag(models.Model):
|
bgneal@138
|
187 """This model represents a user flagging a profile as inappropriate."""
|
bgneal@1206
|
188 user = models.ForeignKey(User, on_delete=models.CASCADE)
|
bgneal@1206
|
189 profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
bgneal@138
|
190 flag_date = models.DateTimeField(auto_now_add=True)
|
bgneal@138
|
191
|
bgneal@138
|
192 def __unicode__(self):
|
bgneal@138
|
193 return u"%s's profile flagged by %s" % (self.profile.user.username,
|
bgneal@138
|
194 self.user.username)
|
bgneal@138
|
195
|
bgneal@138
|
196 class Meta:
|
bgneal@138
|
197 ordering = ('flag_date', )
|
bgneal@138
|
198
|
bgneal@138
|
199 def get_profile_url(self):
|
bgneal@138
|
200 return '<a href="%s">Profile</a>' % self.profile.get_absolute_url()
|
bgneal@138
|
201 get_profile_url.allow_tags = True
|
bgneal@204
|
202
|
bgneal@204
|
203
|
bgneal@204
|
204 class BadgeOwnership(models.Model):
|
bgneal@204
|
205 """This model represents the ownership of badges by users."""
|
bgneal@1206
|
206 profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
bgneal@1206
|
207 badge = models.ForeignKey(Badge, on_delete=models.CASCADE)
|
bgneal@204
|
208 count = models.IntegerField(default=1)
|
bgneal@204
|
209
|
bgneal@204
|
210 class Meta:
|
bgneal@204
|
211 verbose_name_plural = "badge ownership"
|
bgneal@204
|
212 ordering = ('badge__order', )
|
bgneal@204
|
213
|
bgneal@204
|
214 def __unicode__(self):
|
bgneal@204
|
215 if self.count == 1:
|
bgneal@204
|
216 return u"%s owns 1 %s" % (self.profile.user.username,
|
bgneal@204
|
217 self.badge.name)
|
bgneal@204
|
218 else:
|
bgneal@204
|
219 return u"%s owns %d %s badges" % (self.profile.user.username,
|
bgneal@204
|
220 self.count, self.badge.name)
|
bgneal@204
|
221
|
bgneal@204
|
222 def badge_count_str(self):
|
bgneal@204
|
223 if self.count == 1:
|
bgneal@204
|
224 return u"1 %s" % self.badge.name
|
bgneal@204
|
225 return u"%d %ss" % (self.count, self.badge.name)
|