Updating the design of the email opt in end point. Updating API call based on new Org Model.
205 lines
5.8 KiB
Python
205 lines
5.8 KiB
Python
"""Python API for user profiles.
|
|
|
|
Profile information includes a student's demographic information and preferences,
|
|
but does NOT include basic account information such as username, password, and
|
|
email address.
|
|
|
|
"""
|
|
import datetime
|
|
from django.conf import settings
|
|
from django.db import IntegrityError
|
|
import logging
|
|
from pytz import UTC
|
|
|
|
from user_api.models import User, UserProfile, UserPreference, UserOrgTag
|
|
from user_api.helpers import intercept_errors
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
class ProfileRequestError(Exception):
|
|
""" The request to the API was not valid. """
|
|
pass
|
|
|
|
|
|
class ProfileUserNotFound(ProfileRequestError):
|
|
""" The requested user does not exist. """
|
|
pass
|
|
|
|
|
|
class ProfileInvalidField(ProfileRequestError):
|
|
""" The proposed value for a field is not in a valid format. """
|
|
|
|
def __init__(self, field, value):
|
|
self.field = field
|
|
self.value = value
|
|
|
|
def __str__(self):
|
|
return u"Invalid value '{value}' for profile field '{field}'".format(
|
|
value=self.value,
|
|
field=self.field
|
|
)
|
|
|
|
|
|
class ProfileInternalError(Exception):
|
|
""" An error occurred in an API call. """
|
|
pass
|
|
|
|
|
|
FULL_NAME_MAX_LENGTH = 255
|
|
|
|
|
|
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
|
|
def profile_info(username):
|
|
"""Retrieve a user's profile information.
|
|
|
|
Searches either by username or email.
|
|
|
|
At least one of the keyword args must be provided.
|
|
|
|
Args:
|
|
username (unicode): The username of the account to retrieve.
|
|
|
|
Returns:
|
|
dict: If profile information was found.
|
|
None: If the provided username did not match any profiles.
|
|
|
|
"""
|
|
try:
|
|
profile = UserProfile.objects.get(user__username=username)
|
|
except UserProfile.DoesNotExist:
|
|
return None
|
|
|
|
profile_dict = {
|
|
"username": profile.user.username,
|
|
"email": profile.user.email,
|
|
"full_name": profile.name,
|
|
"level_of_education": profile.level_of_education,
|
|
"mailing_address": profile.mailing_address,
|
|
"year_of_birth": profile.year_of_birth,
|
|
"goals": profile.goals,
|
|
"city": profile.city,
|
|
"country": unicode(profile.country),
|
|
}
|
|
|
|
return profile_dict
|
|
|
|
|
|
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
|
|
def update_profile(username, full_name=None):
|
|
"""Update a user's profile.
|
|
|
|
Args:
|
|
username (unicode): The username associated with the account.
|
|
|
|
Keyword Args:
|
|
full_name (unicode): If provided, set the user's full name to this value.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
ProfileRequestError: If there is no profile matching the provided username.
|
|
|
|
"""
|
|
try:
|
|
profile = UserProfile.objects.get(user__username=username)
|
|
except UserProfile.DoesNotExist:
|
|
raise ProfileUserNotFound
|
|
|
|
if full_name is not None:
|
|
name_length = len(full_name)
|
|
if name_length > FULL_NAME_MAX_LENGTH or name_length == 0:
|
|
raise ProfileInvalidField("full_name", full_name)
|
|
else:
|
|
profile.update_name(full_name)
|
|
|
|
|
|
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
|
|
def preference_info(username):
|
|
"""Retrieve information about a user's preferences.
|
|
|
|
Args:
|
|
username (unicode): The username of the account to retrieve.
|
|
|
|
Returns:
|
|
dict: Empty if there is no user
|
|
|
|
"""
|
|
preferences = UserPreference.objects.filter(user__username=username)
|
|
|
|
preferences_dict = {}
|
|
for preference in preferences:
|
|
preferences_dict[preference.key] = preference.value
|
|
|
|
return preferences_dict
|
|
|
|
|
|
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
|
|
def update_preferences(username, **kwargs):
|
|
"""Update a user's preferences.
|
|
|
|
Sets the provided preferences for the given user.
|
|
|
|
Args:
|
|
username (unicode): The username of the account to retrieve.
|
|
|
|
Keyword Args:
|
|
**kwargs (unicode): Arbitrary key-value preference pairs
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
ProfileUserNotFound
|
|
|
|
"""
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
except User.DoesNotExist:
|
|
raise ProfileUserNotFound
|
|
else:
|
|
for key, value in kwargs.iteritems():
|
|
UserPreference.set_preference(user, key, value)
|
|
|
|
|
|
@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
|
|
def update_email_opt_in(username, org, optin):
|
|
"""Updates a user's preference for receiving org-wide emails.
|
|
|
|
Sets a User Org Tag defining the choice to opt in or opt out of organization-wide
|
|
emails.
|
|
|
|
Args:
|
|
username (str): The user to set a preference for.
|
|
org (str): The org is used to determine the organization this setting is related to.
|
|
optin (boolean): True if the user is choosing to receive emails for this organization. If the user is not
|
|
the correct age to receive emails, email-optin is set to False regardless.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
ProfileUserNotFound: Raised when the username specified is not associated with a user.
|
|
|
|
"""
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
except User.DoesNotExist:
|
|
raise ProfileUserNotFound
|
|
|
|
profile = UserProfile.objects.get(user=user)
|
|
of_age = (
|
|
profile.year_of_birth is None or # If year of birth is not set, we assume user is of age.
|
|
datetime.datetime.now(UTC).year - profile.year_of_birth >= # pylint: disable=maybe-no-member
|
|
getattr(settings, 'EMAIL_OPTIN_MINIMUM_AGE', 13)
|
|
)
|
|
|
|
try:
|
|
preference, _ = UserOrgTag.objects.get_or_create(
|
|
user=user, org=org, key='email-optin'
|
|
)
|
|
preference.value = str(optin and of_age)
|
|
preference.save()
|
|
except IntegrityError as err:
|
|
log.warn(u"Could not update organization wide preference due to IntegrityError: {}".format(err.message))
|