annotate bio/models.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents 9e803323a0d0
children 0b6bf9c5a982
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@666 67 # The avatar's name is the username plus the current time plus the file
bgneal@666 68 # extension. The time is used to bust browser caches.
bgneal@666 69 # Get extension, if it exists:
bgneal@560 70 ext = os.path.splitext(filename)[1]
bgneal@560 71 if not ext:
bgneal@560 72 ext = '.jpg'
bgneal@666 73 avatar_name = "{}{}{}".format(instance.user.username,
bgneal@666 74 datetime.datetime.utcnow().strftime('%S%f'), ext)
bgneal@560 75 return os.path.join(settings.AVATAR_DIR, 'users', avatar_name)
gremmie@1 76
gremmie@1 77
gremmie@1 78 class UserProfile(models.Model):
gremmie@1 79 """model to represent additional information about users"""
gremmie@1 80
bgneal@789 81 user = models.OneToOneField(User, related_name='profile')
gremmie@1 82 location = models.CharField(max_length=128, blank=True)
bgneal@609 83 country = models.CharField(max_length=2, blank=True, default='',
bgneal@609 84 choices=bio.flags.FLAG_CHOICES,
bgneal@609 85 help_text='Optional')
gremmie@1 86 birthday = models.DateField(blank=True, null=True,
gremmie@1 87 help_text='Optional; the year is not shown to others')
gremmie@1 88 occupation = models.CharField(max_length=128, blank=True)
gremmie@1 89 interests = models.CharField(max_length=255, blank=True)
gremmie@1 90 profile_text = models.TextField(blank=True)
gremmie@1 91 profile_html = models.TextField(blank=True)
gremmie@1 92 hide_email = models.BooleanField(default=True)
gremmie@1 93 signature = models.TextField(blank=True)
gremmie@1 94 signature_html = models.TextField(blank=True)
gremmie@1 95 avatar = models.ImageField(upload_to=avatar_file_path, blank=True)
bgneal@70 96 time_zone = models.CharField(max_length=64, blank=True,
bgneal@70 97 default='US/Pacific')
bgneal@120 98 use_24_time = models.BooleanField(default=False)
bgneal@96 99 forum_post_count = models.IntegerField(default=0)
bgneal@215 100 status = models.IntegerField(default=STA_STRANGER,
bgneal@147 101 choices=USER_STATUS_CHOICES)
bgneal@147 102 status_date = models.DateTimeField(auto_now_add=True)
bgneal@204 103 badges = models.ManyToManyField(Badge, through="BadgeOwnership")
bgneal@277 104 update_date = models.DateTimeField(db_index=True, blank=True)
bgneal@390 105 auto_favorite = models.BooleanField(default=False)
bgneal@390 106 auto_subscribe = models.BooleanField(default=False)
gremmie@1 107
gremmie@1 108 def __unicode__(self):
gremmie@1 109 return self.user.username
gremmie@1 110
gremmie@1 111 class Meta:
gremmie@1 112 ordering = ('user__username', )
gremmie@1 113
gremmie@1 114 def save(self, *args, **kwargs):
bgneal@562 115 """
bgneal@562 116 Custom profile save() function.
bgneal@562 117 If content_update is True (default), then it is assumed that major
bgneal@562 118 fields are being updated and that the profile_content_update signal
bgneal@562 119 should be signalled. When content_update is False, the update_date is
bgneal@562 120 not updated, expensive markup conversions are not performed, and the
bgneal@562 121 signal is not signalled. This is useful for updating the
bgneal@562 122 forum_post_count, for example.
bgneal@562 123
bgneal@562 124 """
bgneal@562 125 content_update = kwargs.pop('content_update', True)
bgneal@562 126
bgneal@562 127 if content_update:
bgneal@562 128 self.update_date = datetime.datetime.now()
bgneal@562 129 sm = SiteMarkup()
bgneal@562 130 self.profile_html = sm.convert(self.profile_text)
bgneal@562 131 self.signature_html = sm.convert(self.signature)
bgneal@562 132 cache.delete('avatar_' + self.user.username)
bgneal@562 133
gremmie@1 134 super(UserProfile, self).save(*args, **kwargs)
bgneal@562 135
bgneal@562 136 if content_update:
bgneal@562 137 notify_profile_content_update(self)
bgneal@138 138
bgneal@138 139 @models.permalink
bgneal@138 140 def get_absolute_url(self):
bgneal@138 141 return ('bio-view_profile', (), {'username': self.user.username})
bgneal@138 142
bgneal@204 143 def badge_ownership(self):
bgneal@329 144 return BadgeOwnership.objects.filter(profile=self).select_related('badge')
bgneal@204 145
bgneal@215 146 def is_stranger(self):
bgneal@215 147 """Returns True if this user profile status is STA_STRANGER."""
bgneal@215 148 return self.status == STA_STRANGER
bgneal@215 149
bgneal@215 150 def user_is_active(self):
bgneal@215 151 """Returns the profile's user is_active status. This function exists
bgneal@215 152 for the admin.
bgneal@215 153 """
bgneal@215 154 return self.user.is_active
bgneal@215 155 user_is_active.boolean = True
bgneal@215 156 user_is_active.short_description = "Is Active"
bgneal@215 157
bgneal@625 158 def reset(self):
bgneal@363 159 """
bgneal@625 160 Reset profile fields to empty defaults.
bgneal@363 161 This function is useful when a spammer is identified.
bgneal@363 162
bgneal@363 163 """
bgneal@363 164 self.location = ''
bgneal@625 165 self.country = ''
bgneal@625 166 self.birthday = None
bgneal@363 167 self.occupation = ''
bgneal@363 168 self.interests = ''
bgneal@363 169 self.profile_text = ''
bgneal@363 170 self.signature = ''
bgneal@363 171
bgneal@223 172 def search_title(self):
bgneal@223 173 full_name = self.user.get_full_name()
bgneal@223 174 if full_name:
bgneal@223 175 return u"%s (%s)" % (self.user.username, full_name)
bgneal@223 176 return self.user.username
bgneal@223 177
bgneal@223 178 def search_summary(self):
bgneal@277 179 text = render_to_string('search/indexes/bio/userprofile_text.txt',
bgneal@277 180 {'object': self});
bgneal@277 181 return text
bgneal@223 182
bgneal@138 183
bgneal@138 184 class UserProfileFlag(models.Model):
bgneal@138 185 """This model represents a user flagging a profile as inappropriate."""
bgneal@259 186 user = models.ForeignKey(User)
bgneal@138 187 profile = models.ForeignKey(UserProfile)
bgneal@138 188 flag_date = models.DateTimeField(auto_now_add=True)
bgneal@138 189
bgneal@138 190 def __unicode__(self):
bgneal@138 191 return u"%s's profile flagged by %s" % (self.profile.user.username,
bgneal@138 192 self.user.username)
bgneal@138 193
bgneal@138 194 class Meta:
bgneal@138 195 ordering = ('flag_date', )
bgneal@138 196
bgneal@138 197 def get_profile_url(self):
bgneal@138 198 return '<a href="%s">Profile</a>' % self.profile.get_absolute_url()
bgneal@138 199 get_profile_url.allow_tags = True
bgneal@204 200
bgneal@204 201
bgneal@204 202 class BadgeOwnership(models.Model):
bgneal@204 203 """This model represents the ownership of badges by users."""
bgneal@204 204 profile = models.ForeignKey(UserProfile)
bgneal@204 205 badge = models.ForeignKey(Badge)
bgneal@204 206 count = models.IntegerField(default=1)
bgneal@204 207
bgneal@204 208 class Meta:
bgneal@204 209 verbose_name_plural = "badge ownership"
bgneal@204 210 ordering = ('badge__order', )
bgneal@204 211
bgneal@204 212 def __unicode__(self):
bgneal@204 213 if self.count == 1:
bgneal@204 214 return u"%s owns 1 %s" % (self.profile.user.username,
bgneal@204 215 self.badge.name)
bgneal@204 216 else:
bgneal@204 217 return u"%s owns %d %s badges" % (self.profile.user.username,
bgneal@204 218 self.count, self.badge.name)
bgneal@204 219
bgneal@204 220 def badge_count_str(self):
bgneal@204 221 if self.count == 1:
bgneal@204 222 return u"1 %s" % self.badge.name
bgneal@204 223 return u"%d %ss" % (self.count, self.badge.name)
bgneal@562 224
bgneal@562 225 # Put down here to avoid a circular import
bgneal@562 226 from bio.signals import notify_profile_content_update