pulseapi/profiles/models/profiles.py (139 lines of code) (raw):
import os
from django.conf import settings
from django.db import models
from django.utils.html import format_html
from django.utils import timezone
from . import ProfileType
def entry_thumbnail_path(instance, filename):
return 'images/user-avatars/{timestamp}{ext}'.format(
timestamp=str(timezone.now()),
ext=os.path.splitext(filename)[1]
)
class UserProfileQuerySet(models.query.QuerySet):
"""
A queryset for profiles with convenience queries
"""
def limit(self, size):
"""
Limit the results to a fixed size subset
"""
return self[:size]
def active(self):
"""
Return all profiles that have the is_active flag set to True
"""
return self.filter(is_active=True)
class UserProfile(models.Model):
"""
This class houses all user profile information,
such as real name, social media information,
bookmarks on the site, etc.
"""
# make sure we record when a profile got created.
created_at = models.DateTimeField(
auto_now_add=True
)
# This flag determines whether this profile has been
# activated, meaning it can be retrieved through REST
# API calls and might get crosslinked into data structures
# that rely on knowing a user or group's profile.
is_active = models.BooleanField(
default=False
)
# "user X bookmarked entry Y" is a many to many relation,
# for which we also want to know *when* a user bookmarked
# a specific entry. As such, we use a helper class that
# tracks this relation as well as the time it's created.
bookmarks = models.ManyToManyField(
'entries.Entry',
through='profiles.UserBookmarks'
)
# The name field is an alternative name to be used if the
# associated user(s) don't want to expose their login-indicated
# name instead.
#
# Examples of this are nicknames, pseudonyms, and org names
custom_name = models.CharField(
max_length=140,
blank=True
)
custom_name.short_description = 'Custom user name'
# Accessing the Profile-indicated name needs various stages
# of fallback.
# Note that we cannot use this accessor as a lookup field in querysets
# because it is not an actual field.
@property
def name(self):
custom_name = self.custom_name
# blank values, including pure whitespace, don't count:
if not custom_name or not custom_name.strip():
user = self.user
return user.name if user else None
# anything else does count.
return custom_name
# We provide an easy accessor to the profile's user because
# accessing the reverse relation (using related_name) can throw
# a RelatedObjectDoesNotExist exception for orphan profiles.
# This allows us to return None instead.
#
# Note: we cannot use this accessor as a lookup field in querysets
# because it is not an actual field.
@property
def user(self):
# Import EmailUser here to avoid circular import
from pulseapi.users.models import EmailUser
try:
return self.related_user
except EmailUser.DoesNotExist:
return None
# This flag marks whether or not this profile applies to
# "A human being", or a group of people (be that a club, org,
# institution, etc. etc.)
is_group = models.BooleanField(
default=False
)
is_group.short_description = 'This is a group profile.'
# We deal with location by asking users to just write their
# location as they would if they were search maps for it.
location = models.CharField(
max_length=1024,
blank=True
)
location.short_description = 'User location (as would be typed in a maps search)'
# Thumbnail image for this user; their "avatar" even though we
# do not have anything on the site right now where avatars
# come into play.
thumbnail = models.ImageField(
max_length=2048,
upload_to=entry_thumbnail_path,
blank=True
)
def thumbnail_image_tag(self):
if not self.thumbnail:
return format_html('<span>No image to preview</span>')
media_url = settings.MEDIA_URL
if settings.USE_S3:
media_url = 'https://{domain}/{bucket}/'.format(
domain=settings.AWS_S3_CUSTOM_DOMAIN,
bucket=settings.AWS_LOCATION
)
html = '<img src="{media_url}{src}" style="width:25%">'.format(
media_url=media_url,
src=self.thumbnail
)
return format_html(html)
thumbnail_image_tag.short_description = 'Thumbnail preview'
# Which issues does this user care about/are they involved in?
issues = models.ManyToManyField(
'issues.Issue',
blank=True,
)
# we allow users to indicate several possible predefined service URLs
twitter = models.URLField(
max_length=2048,
blank=True
)
linkedin = models.URLField(
max_length=2048,
blank=True
)
github = models.URLField(
max_length=2048,
blank=True
)
website = models.URLField(
max_length=2048,
blank=True
)
# --- extended information ---
enable_extended_information = models.BooleanField(
default=False
)
# TODO: Deprecate
profile_type = models.ForeignKey(
'profiles.ProfileType',
null=True,
blank=True,
on_delete=models.SET_NULL
# default is handled in save()
)
# TODO: Deprecate
program_type = models.ForeignKey(
'profiles.ProgramType',
null=True,
blank=True,
on_delete=models.SET_NULL
)
# TODO: Deprecate
program_year = models.ForeignKey(
'profiles.ProgramYear',
null=True,
blank=True,
on_delete=models.SET_NULL
)
# Free form affiliation information
affiliation = models.CharField(
max_length=200,
blank=True
)
# A tweet-style user bio
user_bio = models.CharField(
max_length=212,
blank=True
)
# A long-form user bio
user_bio_long = models.TextField(
max_length=4096,
blank=True
)
objects = UserProfileQuerySet.as_manager()
def save(self, *args, **kwargs):
if self.profile_type is None:
self.profile_type = ProfileType.get_default_profile_type()
super(UserProfile, self).save(*args, **kwargs)
def __str__(self):
if self.user is None:
return f'{self.custom_name} (no user)'
return f'{self.name} ({self.user.email})'
class Meta:
verbose_name = "Profile"