annotate bio/models.py @ 631:f36d1a168be7

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