gremmie@1: """
gremmie@1: Contains models for the bio application.
gremmie@1: I would have picked profile for this application, but that is already taken, apparently.
gremmie@1: """
bgneal@277: import datetime
gremmie@1: import os.path
gremmie@1:
gremmie@1: from django.db import models
bgneal@259: from django.contrib.auth.models import User
gremmie@1: from django.conf import settings
bgneal@54: from django.core.cache import cache
bgneal@1035: from django.core.urlresolvers import reverse
bgneal@277: from django.template.loader import render_to_string
gremmie@1:
bgneal@128: from core.markup import SiteMarkup
bgneal@609: import bio.flags
bgneal@919: from bio.signals import notify_profile_content_update
bgneal@124:
bgneal@562:
bgneal@215: # These are the secondary user status enumeration values.
bgneal@215: (STA_ACTIVE, # User is a full member in good standing.
bgneal@215: STA_RESIGNED, # User has voluntarily asked to be removed.
bgneal@215: STA_REMOVED, # User was removed for bad behavior.
bgneal@215: STA_SUSPENDED, # User is temporarily suspended; e.g. a stranger tripped
bgneal@215: # the spam filter.
bgneal@215: STA_SPAMMER, # User has been removed for spamming.
bgneal@215: STA_STRANGER, # New member, isn't fully trusted yet. Their comments and
bgneal@215: # forum posts are scanned for spam. They can have their
bgneal@215: # accounts deactivated by moderators for spamming.
bgneal@215: ) = range(6)
bgneal@147:
bgneal@147: USER_STATUS_CHOICES = (
bgneal@147: (STA_ACTIVE, "Active"),
bgneal@147: (STA_RESIGNED, "Resigned"),
bgneal@147: (STA_REMOVED, "Removed"),
bgneal@147: (STA_SUSPENDED, "Suspended"),
bgneal@147: (STA_SPAMMER, "Spammer"),
bgneal@215: (STA_STRANGER, "Stranger")
bgneal@147: )
bgneal@147:
bgneal@204:
bgneal@204: class Badge(models.Model):
bgneal@204: """This model represents badges that users can earn."""
bgneal@204: image = models.ImageField(upload_to='badges')
bgneal@204: name = models.CharField(max_length=64)
bgneal@204: description = models.TextField(blank=True)
bgneal@204: order = models.IntegerField()
bgneal@204: numeric_id = models.IntegerField(db_index=True)
bgneal@204:
bgneal@204: class Meta:
bgneal@204: ordering = ('order', )
bgneal@204:
bgneal@204: def __unicode__(self):
bgneal@204: return self.name
bgneal@204:
bgneal@204: def get_absolute_url(self):
bgneal@204: return self.image.url
bgneal@204:
bgneal@204: def html(self):
bgneal@204: """Returns a HTML img tag representation of the badge."""
bgneal@204: if self.image:
bgneal@204: return u'' % (
bgneal@204: self.get_absolute_url(), self.name, self.name)
bgneal@204: return u''
bgneal@204: html.allow_tags = True
bgneal@204:
bgneal@204:
gremmie@1: def avatar_file_path(instance, filename):
bgneal@666: # The avatar's name is the username plus the current time plus the file
bgneal@666: # extension. The time is used to bust browser caches.
bgneal@666: # Get extension, if it exists:
bgneal@560: ext = os.path.splitext(filename)[1]
bgneal@560: if not ext:
bgneal@560: ext = '.jpg'
bgneal@666: avatar_name = "{}{}{}".format(instance.user.username,
bgneal@666: datetime.datetime.utcnow().strftime('%S%f'), ext)
bgneal@560: return os.path.join(settings.AVATAR_DIR, 'users', avatar_name)
gremmie@1:
gremmie@1:
gremmie@1: class UserProfile(models.Model):
gremmie@1: """model to represent additional information about users"""
gremmie@1:
bgneal@789: user = models.OneToOneField(User, related_name='profile')
gremmie@1: location = models.CharField(max_length=128, blank=True)
bgneal@609: country = models.CharField(max_length=2, blank=True, default='',
bgneal@609: choices=bio.flags.FLAG_CHOICES,
bgneal@609: help_text='Optional')
gremmie@1: birthday = models.DateField(blank=True, null=True,
gremmie@1: help_text='Optional; the year is not shown to others')
gremmie@1: occupation = models.CharField(max_length=128, blank=True)
gremmie@1: interests = models.CharField(max_length=255, blank=True)
gremmie@1: profile_text = models.TextField(blank=True)
gremmie@1: profile_html = models.TextField(blank=True)
gremmie@1: hide_email = models.BooleanField(default=True)
gremmie@1: signature = models.TextField(blank=True)
gremmie@1: signature_html = models.TextField(blank=True)
gremmie@1: avatar = models.ImageField(upload_to=avatar_file_path, blank=True)
bgneal@70: time_zone = models.CharField(max_length=64, blank=True,
bgneal@70: default='US/Pacific')
bgneal@120: use_24_time = models.BooleanField(default=False)
bgneal@96: forum_post_count = models.IntegerField(default=0)
bgneal@215: status = models.IntegerField(default=STA_STRANGER,
bgneal@147: choices=USER_STATUS_CHOICES)
bgneal@147: status_date = models.DateTimeField(auto_now_add=True)
bgneal@204: badges = models.ManyToManyField(Badge, through="BadgeOwnership")
bgneal@277: update_date = models.DateTimeField(db_index=True, blank=True)
bgneal@390: auto_favorite = models.BooleanField(default=False)
bgneal@390: auto_subscribe = models.BooleanField(default=False)
gremmie@1:
gremmie@1: def __unicode__(self):
gremmie@1: return self.user.username
gremmie@1:
gremmie@1: class Meta:
gremmie@1: ordering = ('user__username', )
gremmie@1:
gremmie@1: def save(self, *args, **kwargs):
bgneal@562: """
bgneal@562: Custom profile save() function.
bgneal@562: If content_update is True (default), then it is assumed that major
bgneal@562: fields are being updated and that the profile_content_update signal
bgneal@562: should be signalled. When content_update is False, the update_date is
bgneal@562: not updated, expensive markup conversions are not performed, and the
bgneal@562: signal is not signalled. This is useful for updating the
bgneal@562: forum_post_count, for example.
bgneal@562:
bgneal@562: """
bgneal@562: content_update = kwargs.pop('content_update', True)
bgneal@562:
bgneal@562: if content_update:
bgneal@562: self.update_date = datetime.datetime.now()
bgneal@562: sm = SiteMarkup()
bgneal@562: self.profile_html = sm.convert(self.profile_text)
bgneal@562: self.signature_html = sm.convert(self.signature)
bgneal@562: cache.delete('avatar_' + self.user.username)
bgneal@562:
gremmie@1: super(UserProfile, self).save(*args, **kwargs)
bgneal@562:
bgneal@562: if content_update:
bgneal@562: notify_profile_content_update(self)
bgneal@138:
bgneal@138: def get_absolute_url(self):
bgneal@1035: return reverse('bio-view_profile', kwargs={'username': self.user.username})
bgneal@138:
bgneal@204: def badge_ownership(self):
bgneal@329: return BadgeOwnership.objects.filter(profile=self).select_related('badge')
bgneal@204:
bgneal@215: def is_stranger(self):
bgneal@215: """Returns True if this user profile status is STA_STRANGER."""
bgneal@215: return self.status == STA_STRANGER
bgneal@215:
bgneal@215: def user_is_active(self):
bgneal@215: """Returns the profile's user is_active status. This function exists
bgneal@215: for the admin.
bgneal@215: """
bgneal@215: return self.user.is_active
bgneal@215: user_is_active.boolean = True
bgneal@215: user_is_active.short_description = "Is Active"
bgneal@215:
bgneal@625: def reset(self):
bgneal@363: """
bgneal@625: Reset profile fields to empty defaults.
bgneal@363: This function is useful when a spammer is identified.
bgneal@363:
bgneal@363: """
bgneal@363: self.location = ''
bgneal@625: self.country = ''
bgneal@625: self.birthday = None
bgneal@363: self.occupation = ''
bgneal@363: self.interests = ''
bgneal@363: self.profile_text = ''
bgneal@363: self.signature = ''
bgneal@363:
bgneal@223: def search_title(self):
bgneal@223: full_name = self.user.get_full_name()
bgneal@223: if full_name:
bgneal@223: return u"%s (%s)" % (self.user.username, full_name)
bgneal@223: return self.user.username
bgneal@223:
bgneal@223: def search_summary(self):
bgneal@277: text = render_to_string('search/indexes/bio/userprofile_text.txt',
bgneal@277: {'object': self});
bgneal@277: return text
bgneal@223:
bgneal@138:
bgneal@138: class UserProfileFlag(models.Model):
bgneal@138: """This model represents a user flagging a profile as inappropriate."""
bgneal@259: user = models.ForeignKey(User)
bgneal@138: profile = models.ForeignKey(UserProfile)
bgneal@138: flag_date = models.DateTimeField(auto_now_add=True)
bgneal@138:
bgneal@138: def __unicode__(self):
bgneal@138: return u"%s's profile flagged by %s" % (self.profile.user.username,
bgneal@138: self.user.username)
bgneal@138:
bgneal@138: class Meta:
bgneal@138: ordering = ('flag_date', )
bgneal@138:
bgneal@138: def get_profile_url(self):
bgneal@138: return 'Profile' % self.profile.get_absolute_url()
bgneal@138: get_profile_url.allow_tags = True
bgneal@204:
bgneal@204:
bgneal@204: class BadgeOwnership(models.Model):
bgneal@204: """This model represents the ownership of badges by users."""
bgneal@204: profile = models.ForeignKey(UserProfile)
bgneal@204: badge = models.ForeignKey(Badge)
bgneal@204: count = models.IntegerField(default=1)
bgneal@204:
bgneal@204: class Meta:
bgneal@204: verbose_name_plural = "badge ownership"
bgneal@204: ordering = ('badge__order', )
bgneal@204:
bgneal@204: def __unicode__(self):
bgneal@204: if self.count == 1:
bgneal@204: return u"%s owns 1 %s" % (self.profile.user.username,
bgneal@204: self.badge.name)
bgneal@204: else:
bgneal@204: return u"%s owns %d %s badges" % (self.profile.user.username,
bgneal@204: self.count, self.badge.name)
bgneal@204:
bgneal@204: def badge_count_str(self):
bgneal@204: if self.count == 1:
bgneal@204: return u"1 %s" % self.badge.name
bgneal@204: return u"%d %ss" % (self.count, self.badge.name)