users package

Subpackages

Submodules

users.admin module

users.apps module

class users.apps.UsersConfig(app_name, app_module)[source]

Bases: AppConfig

name = 'users'

users.backends module

class users.backends.EmailBackend[source]

Bases: ModelBackend

authenticate(request, email=None, password=None, **kwargs)[source]

users.email_utils module

users.email_utils.send_verification_email(user)[source]

Send an email verification link to the given user via SendGrid.

Constructs an HTML email containing a verification link built from settings.FRONTEND_URL and the user’s email_verification_token. The link routes to /verify-email?token=<token> on the frontend.

In test mode (settings.SEND_EMAILS = False) no email is sent; the verification link is logged at INFO level instead and the function returns True to allow the rest of the registration flow to proceed unaffected.

In production, a SendGrid API call is made using settings.SENDGRID_API_KEY. Any non-2xx response or raised exception is caught, logged at ERROR level, and returned as False — callers are responsible for deciding whether to surface this failure to the user.

Parameters:

user (users.models.User) – The user to whom the verification email should be sent. Must have a non-empty email_verification_token; returns False immediately if the token is absent.

Returns:

True if the email was sent successfully (or skipped in test mode), False if the token is missing, SendGrid returns a non-2xx status, or any exception is raised during the API call.

Return type:

bool

Required settings:

  • SEND_EMAILSFalse to suppress sending in test/dev environments.

  • FRONTEND_URL — Base URL used to construct the verification link.

  • DEFAULT_FROM_EMAIL — Sender address for all outbound mail.

  • SENDGRID_API_KEY — API key for the SendGrid client.

Example:

>>> token = user.generate_verification_token()
>>> success = send_verification_email(user)
>>> if not success:
...     logger.warning('Verification email failed; user must request resend.')
users.email_utils.send_password_reset_email(user)[source]

Send a password reset link to the given user via SendGrid.

Constructs an HTML email containing a reset link built from settings.FRONTEND_URL and the user’s password_reset_token. The link routes to /reset-password/<token> on the frontend.

In test mode (settings.SEND_EMAILS = False) no email is sent; the reset link is logged at INFO level instead and the function returns True to allow the password reset flow to proceed unaffected.

In production, a SendGrid API call is made using settings.SENDGRID_API_KEY. Any non-2xx response or raised exception is caught, logged at ERROR level, and returned as False — callers are responsible for deciding whether to surface this failure to the user.

Parameters:

user (users.models.User) – The user to whom the reset email should be sent. Must have a non-empty password_reset_token; returns False immediately if the token is absent.

Returns:

True if the email was sent successfully (or skipped in test mode), False if the token is missing, SendGrid returns a non-2xx status, or any exception is raised during the API call.

Return type:

bool

Required settings:

  • SEND_EMAILSFalse to suppress sending in test/dev environments.

  • FRONTEND_URL — Base URL used to construct the reset link.

  • DEFAULT_FROM_EMAIL — Sender address for all outbound mail.

  • SENDGRID_API_KEY — API key for the SendGrid client.

Example:

>>> token = user.generate_password_reset_token()
>>> success = send_password_reset_email(user)
>>> if not success:
...     logger.warning('Reset email failed; user must request a new link.')

users.models module

class users.models.UserManager(*args, **kwargs)[source]

Bases: BaseUserManager

Custom manager for the User model.

Provides helper methods for creating standard users and superusers, enforcing UCLA email validation and normalization on all accounts.

create_user(email, password=None, **extra_fields)[source]

Create and persist a standard (non-superuser) User instance.

Normalizes the email to lowercase and strips whitespace before saving. Raises ValueError if the email is missing or not a valid UCLA address.

Parameters:
  • email (str) – The user’s UCLA email address.

  • password (str, optional) – The user’s plain-text password. Hashed before storage.

  • extra_fields – Additional model field values (e.g. firstName, role).

Returns:

The newly created and saved User instance.

Return type:

User

Raises:

ValueError – If email is empty or not a recognized UCLA domain.

Example:

>>> user = User.objects.create_user('jdoe@ucla.edu', 'secret123')
create_superuser(email, password=None, **extra_fields)[source]

Create and persist a superuser User instance with admin privileges.

Defaults is_staff, is_superuser, and role to their administrative values before delegating to create_user().

Parameters:
  • email (str) – The superuser’s UCLA email address.

  • password (str, optional) – The superuser’s plain-text password.

  • extra_fields – Additional model field overrides.

Returns:

The newly created superuser User instance.

Return type:

User

Raises:

ValueError – If email is empty or not a recognized UCLA domain.

class users.models.User(*args, **kwargs)[source]

Bases: AbstractBaseUser, PermissionsMixin

Custom user model for the BruinBridge platform.

Replaces Django’s default User with a UCLA-email-gated account that supports mentor/mentee matching, soft deletion, email verification, and password reset workflows. Authentication is performed via email instead of username.

userID

Immutable public identifier for the user.

Type:

UUIDField

email

Unique UCLA email address; used as the login credential.

Type:

EmailField

firstName

User’s given name.

Type:

CharField

lastName

User’s family name.

Type:

CharField

role

One of MENTEE, MENTOR, or ADMIN (see Role).

Type:

CharField

profilePictureUrl

URL pointing to the user’s avatar image.

Type:

URLField

year

Academic year (0–5).

Type:

IntegerField

major

List of declared majors.

Type:

JSONField

minor

List of declared minors.

Type:

JSONField

international

Flag for international student status.

Type:

BooleanField

commuter

Flag for commuter status.

Type:

BooleanField

firstgen

Flag for first-generation college student status.

Type:

BooleanField

outofstate

Flag for out-of-state student status.

Type:

BooleanField

transfer

Flag for transfer student status.

Type:

BooleanField

otherBackground

Free-text description of additional background.

Type:

CharField

hobbies

User-provided list of hobbies.

Type:

CharField

clubs

User-provided list of club memberships.

Type:

CharField

goals

User-provided academic or career goals.

Type:

CharField

isMatched

Whether the user currently has an active match.

Type:

BooleanField

matchedMentorEmail

Email of the mentor currently matched to this mentee.

Type:

EmailField

blacklisted_mentors

List of mentor emails the mentee has blocked.

Type:

JSONField

current_mentees

List of mentee emails currently assigned to this mentor.

Type:

JSONField

is_deleted

Soft-deletion flag; account is hidden but not yet purged.

Type:

BooleanField

deletion_requested_date

When the soft-deletion was requested.

Type:

DateTimeField

permanent_deletion_date

When the account will be hard-deleted.

Type:

DateTimeField

is_verified

Whether the user’s email address has been verified.

Type:

BooleanField

email_verification_token

Active one-time email verification token.

Type:

CharField

email_verification_sent_at

When the verification email was dispatched.

Type:

DateTimeField

password_reset_token

Active one-time password reset token.

Type:

CharField

password_reset_sent_at

When the password reset email was dispatched.

Type:

DateTimeField

is_active

Django internal; False disables login.

Type:

BooleanField

is_staff

Django internal; grants admin-site access.

Type:

BooleanField

date_joined

Timestamp of account creation.

Type:

DateTimeField

class Role(*values)[source]

Bases: TextChoices

Enumeration of valid user roles on the platform.

Variables:
  • MENTEE – A student seeking guidance from a mentor.

  • MENTOR – A student providing guidance to mentees.

  • ADMIN – A platform administrator with elevated privileges.

MENTEE = 'MENTEE'
MENTOR = 'MENTOR'
ADMIN = 'ADMIN'
userID

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

email

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

firstName

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

lastName

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

role

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

profilePictureUrl

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

year

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

major

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

minor

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

international

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

commuter

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

firstgen

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

outofstate

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

transfer

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

otherBackground

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

hobbies

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

clubs

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

goals

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

isMatched

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

matchedMentorEmail

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

blacklisted_mentors

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

current_mentees

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_deleted

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

deletion_requested_date

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

permanent_deletion_date

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_verified

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

email_verification_token

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

email_verification_sent_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

password_reset_token

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

password_reset_sent_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_active

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_staff

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

date_joined

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <users.models.UserManager object>
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
clean()[source]

Normalize and validate model fields before saving.

Strips whitespace and lowercases email, then asserts it belongs to a recognized UCLA domain.

Raises:

ValidationError – If email is not a valid UCLA address.

save(*args, **kwargs)[source]

Run full model validation then persist the instance.

Calls full_clean() before every save to ensure email normalization and UCLA domain constraints are always enforced.

Parameters:
  • args – Positional arguments forwarded to super().save().

  • kwargs – Keyword arguments forwarded to super().save().

Raises:

ValidationError – If any field fails validation in clean().

SURVEY_FIELDS = ('firstName', 'lastName', 'year', 'major', 'minor', 'hobbies', 'clubs', 'goals', 'otherBackground', 'international', 'commuter', 'firstgen', 'outofstate', 'transfer', 'profilePictureUrl')

Fields that may be set via update_profile() or complete_survey(). Any field absent from this tuple will be silently ignored, protecting sensitive fields (e.g. email, password) from bulk updates.

SAFE_ROLES = {User.Role.MENTEE, User.Role.MENTOR}

Roles that a user is permitted to self-assign. ADMIN is intentionally excluded.

update_profile(**kwargs)[source]

Update permitted profile fields with the supplied values.

Only fields listed in SURVEY_FIELDS are written. Role changes are accepted only for SAFE_ROLES (MENTOR / MENTEE); any attempt to assign ADMIN is silently dropped and logged as a warning.

Parameters:

kwargs – Mapping of field names to new values.

Raises:

ValidationError – Propagated from save() if a written value fails model-level validation.

Example:

>>> user.update_profile(firstName='Jane', year=2)
complete_survey(survey_data)[source]

Persist the user’s initial onboarding survey responses.

Delegates entirely to update_profile(), applying the same field whitelist and role-safety rules.

Parameters:

survey_data (dict) – Dictionary of survey field names to their submitted values.

Raises:

ValidationError – Propagated from update_profile() if a value fails model-level validation.

Note

This method may be redundant if update_profile() already satisfies all survey update requirements.

change_role(new_role)[source]

Switch the user’s role between MENTEE and MENTOR.

Before applying the role change, all existing matches, compatibility scores, and chat messages are cleaned up to prevent stale data from persisting across roles.

Parameters:

new_role (str) – The target role. Must be 'MENTEE' or 'MENTOR'.

Raises:

ValueError – If new_role is not MENTEE or MENTOR, or if the user already holds new_role.

Example:

>>> user.change_role('MENTOR')
blacklist_mentor(mentor_email)[source]

Add a mentor’s email to this mentee’s blacklist.

Prevents the blacklisted mentor from being matched to this mentee in future matching runs. Does nothing if the mentor is already blacklisted.

Parameters:

mentor_email (str) – The email address of the mentor to blacklist.

Example:

>>> mentee.blacklist_mentor('mentor@ucla.edu')
add_mentee(mentee_email)[source]

Assign a mentee to this mentor’s active roster.

Performs the following checks before adding:

  • Caller must have the MENTOR role.

  • Mentor roster must not already be at capacity (MAX_MENTEES).

  • Mentee must not already be on the roster.

  • Mentee must exist in the database.

  • This mentor must not be on the mentee’s blacklist.

On success, updates isMatched for both parties. The mentor’s isMatched flag is set to True only when the roster reaches full capacity.

Parameters:

mentee_email (str) – Email address of the mentee to add.

Returns:

True if the mentee was successfully added, False if any pre-condition was not met.

Return type:

bool

Example:

>>> mentor.add_mentee('mentee@g.ucla.edu')
True
remove_mentee(mentee_email)[source]

Remove a mentee from this mentor’s active roster.

Clears isMatched for both the mentor and the removed mentee. Does nothing if mentee_email is not currently on the roster.

Parameters:

mentee_email (str) – Email address of the mentee to remove.

Example:

>>> mentor.remove_mentee('mentee@g.ucla.edu')
request_deletion()[source]

Initiate a soft-delete of the account with a configurable grace period.

Sets is_deleted to True and schedules permanent_deletion_date as now + ACCOUNT_RECOVERY_GRACE_PERIOD hours (sourced from settings.ACCOUNT_RECOVERY_GRACE_PERIOD). Also unlinks all active matches, removes compatibility scores, and deletes chat history.

The account remains recoverable via cancel_deletion() until permanent_deletion_date is reached.

cancel_deletion()[source]

Restore a soft-deleted account within the grace period.

Clears is_deleted, deletion_requested_date, and permanent_deletion_date. Returns False without making changes if the grace period has already elapsed.

Returns:

True if the account was successfully restored, False if the grace period has expired.

Return type:

bool

Example:

>>> user.cancel_deletion()
True
static permanently_delete_expired_accounts()[source]

Hard-delete all accounts whose grace period has elapsed.

Queries for User records where is_deleted=True and permanent_deletion_date is in the past, marks each as is_active=False, then performs a hard database delete.

Intended to be called periodically by a scheduled task (e.g. a Celery beat job or management command).

Returns:

The number of accounts permanently deleted.

Return type:

int

Example:

>>> deleted = User.permanently_delete_expired_accounts()
>>> print(f'{deleted} accounts purged.')
generate_verification_token()[source]

Generate, persist, and return a fresh email verification token.

Creates a UUID4 token, records the current timestamp as email_verification_sent_at, and writes both fields to the database without triggering a full model save.

Returns:

The newly generated verification token string.

Return type:

str

Example:

>>> token = user.generate_verification_token()
>>> send_verification_email(user.email, token)
verify_email(token)[source]

Validate an email verification token and mark the account as verified.

Checks that token matches email_verification_token and that the token was issued within the window defined by settings.EMAIL_VERIFICATION_TOKEN_EXPIRATION (hours). On success, sets is_verified=True and clears both token fields.

Parameters:

token (str) – The verification token submitted by the user.

Returns:

True if verification succeeded, False if the token was missing, mismatched, or expired.

Return type:

bool

Example:

>>> user.verify_email('3f2e1a...')
True
generate_password_reset_token()[source]

Generate, persist, and return a fresh password reset token.

Creates a UUID4 token, records the current timestamp as password_reset_sent_at, and writes both fields to the database without triggering a full model save.

Returns:

The newly generated password reset token string.

Return type:

str

Example:

>>> token = user.generate_password_reset_token()
>>> send_reset_email(user.email, token)
reset_password_with_token(token, new_password)[source]

Validate a password reset token and apply a new password.

Checks that token matches password_reset_token and that the token was issued within the window defined by settings.PASSWORD_RESET_TOKEN_EXPIRATION (hours). On success, hashes and saves new_password, then clears both token fields.

Parameters:
  • token (str) – The password reset token submitted by the user.

  • new_password (str) – The new plain-text password to set.

Returns:

True if the password was successfully reset, False if the token was missing, mismatched, or expired.

Return type:

bool

Example:

>>> user.reset_password_with_token('a1b2c3...', 'newSecurePass!')
True
exception DoesNotExist

Bases: ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: MultipleObjectsReturned

exception NotUpdated

Bases: ObjectNotUpdated, DatabaseError

get_next_by_date_joined(*, field=<django.db.models.fields.DateTimeField: date_joined>, is_next=True, **kwargs)
get_previous_by_date_joined(*, field=<django.db.models.fields.DateTimeField: date_joined>, is_next=False, **kwargs)
get_role_display(*, field=<django.db.models.fields.CharField: role>)
groups

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_superuser

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

last_login

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

logentry_set

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_reverse_many_to_one_manager() defined below.

password

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

user_permissions

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

users.serializers module

users.serializers.full_name(user)[source]

Derive a display name from a user’s firstName and lastName fields.

Strips whitespace from each component before joining. Falls back to 'Not provided' if both fields are blank or absent, ensuring the return value is always a non-empty string suitable for display.

Parameters:

user (users.models.User) – The user whose name is being formatted.

Returns:

Full name string, or 'Not provided' if both name fields are empty.

Return type:

str

Example:

>>> full_name(user)          # both fields set
'Jane Doe'
>>> full_name(empty_user)    # both fields blank
'Not provided'
users.serializers.serialize_user(user)[source]

Serialize a User instance into a standard API response dict.

Includes core identity, academic profile, background flags, and matching state. Excludes sensitive fields such as password, verification tokens, and password reset tokens.

Note

If the User model fields change, update this function and serialize_user_with_deletion() accordingly.

Parameters:

user (users.models.User) – The user instance to serialize.

Returns:

Dictionary of serialized user data safe for API responses.

Return type:

dict

Response shape:

{
    "email": "jdoe@ucla.edu",
    "firstName": "Jane",
    "lastName": "Doe",
    "role": "MENTEE",
    "year": 2,
    "major": ["Computer Science"],
    "minor": [],
    "hobbies": "chess, hiking",
    "clubs": "ACM",
    "goals": "Become a software engineer",
    "international": false,
    "commuter": false,
    "firstgen": true,
    "outofstate": false,
    "transfer": false,
    "otherBackground": "",
    "isMatched": false,
    "profilePictureUrl": "https://..."
}
users.serializers.serialize_user_with_deletion(user)[source]

Serialize a User instance including soft-deletion metadata.

Extends serialize_user() with is_deleted and permanent_deletion_date, intended for responses where the client needs to display a deletion warning or countdown — for example, the login response when an account is within its grace period.

permanent_deletion_date is serialized as an ISO 8601 string when present, or null if the date has not been set.

Parameters:

user (users.models.User) – The soft-deleted (or deletion-pending) user instance to serialize.

Returns:

Dictionary of serialized user data including deletion fields.

Return type:

dict

Additional fields beyond serialize_user():

{
    "is_deleted": true,
    "permanent_deletion_date": "2024-01-15T10:30:00+00:00"
}

Example:

>>> serialize_user_with_deletion(pending_user)
{
    "email": "jdoe@ucla.edu",
    ...,
    "is_deleted": True,
    "permanent_deletion_date": "2024-01-15T10:30:00+00:00"
}

users.services module

exception users.services.UserServiceError(message, status=400)[source]

Bases: Exception

Base exception for all user service layer errors.

Carries an HTTP status code alongside the message so that views can return the appropriate response status without additional branching logic.

Parameters:
  • message (str) – Human-readable description of the error.

  • status (int) – HTTP status code to return to the client. Defaults to 400.

Example:

raise UserServiceError('Something went wrong', status=400)
exception users.services.NotFound(message='User not found')[source]

Bases: UserServiceError

Raised when a requested user account cannot be found.

Specialization of UserServiceError that always sets status=404.

Parameters:

message (str) – Human-readable description of the missing resource. Defaults to 'User not found'.

Example:

raise NotFound('User not found')
exception users.services.Conflict(message)[source]

Bases: UserServiceError

Raised when a user operation conflicts with the current system state.

Specialization of UserServiceError that always sets status=409. Typical use case: attempting to register with an email address that is already in use.

Parameters:

message (str) – Human-readable description of the conflict.

Example:

raise Conflict('Email already exists')
exception users.services.Unauthorized(message='Invalid credentials')[source]

Bases: UserServiceError

Raised when authentication credentials are missing or invalid.

Specialization of UserServiceError that always sets status=401.

Parameters:

message (str) – Human-readable description of the auth failure. Defaults to 'Invalid credentials'.

Example:

raise Unauthorized()
exception users.services.Forbidden(message)[source]

Bases: UserServiceError

Raised when an authenticated user attempts an action they are not permitted to perform.

Specialization of UserServiceError that always sets status=403.

Parameters:

message (str) – Human-readable description of the authorization failure.

Example:

raise Forbidden('Email not verified')
users.services.register_user(email, password, role='MENTEE')[source]

Create a new user account and dispatch a verification email.

Handles the following edge cases before creating the account:

  • If an account with email already exists and its grace period has expired, it is hard-deleted and registration proceeds.

  • If an account exists and is within its grace period (or is not soft-deleted at all), a Conflict is raised.

In test mode (settings.SEND_EMAILS = False) the account is automatically marked as verified and no email is sent. In production, a verification token is generated and dispatched via send_verification_email(). A failed email send is logged as a warning but does not prevent the account from being created.

Parameters:
  • email (str) – UCLA email address for the new account.

  • password (str) – Plain-text password; hashed before storage.

  • role (str) – Initial role for the account. Defaults to 'MENTEE'.

Returns:

The newly created User instance.

Return type:

users.models.User

Raises:
  • Conflict – If a non-expired account already exists for email (HTTP 409).

  • ValueError – Propagated from create_user() if email is not a valid UCLA address.

Example:

>>> user = register_user('jdoe@ucla.edu', 'secret123', role='MENTOR')
users.services.authenticate_user(request, email, password)[source]

Verify credentials and return the authenticated user.

Delegates to Django’s authenticate() backend, then enforces an additional email-verification check. Does not open a session — callers are responsible for calling login() if a session is required.

Parameters:
  • request (django.http.HttpRequest) – The current HTTP request, forwarded to Django’s auth backend.

  • email (str) – The user’s UCLA email address.

  • password (str) – The user’s plain-text password.

Returns:

The authenticated User instance.

Return type:

users.models.User

Raises:
  • Unauthorized – If Django’s auth backend returns None, indicating invalid credentials (HTTP 401).

  • Forbidden – If the account exists but the email address has not yet been verified (HTTP 403).

Example:

>>> user = authenticate_user(request, 'jdoe@ucla.edu', 'secret123')
users.services.complete_survey(user, survey_data)[source]

Apply onboarding survey responses to a user’s profile.

Thin delegation to complete_survey(), which enforces the field whitelist and role-safety rules defined on the model.

Parameters:
  • user (users.models.User) – The user completing the survey.

  • survey_data (dict) – Dictionary of survey field names to their submitted values.

Raises:

ValidationError – Propagated from the model if any submitted value fails field-level validation.

Example:

>>> complete_survey(user, {'firstName': 'Jane', 'year': 1})
users.services.request_deletion(user, confirmation_text)[source]

Initiate a soft-delete of the user’s account after confirming intent.

Requires the caller to supply the user’s own email address as confirmation_text. On success, delegates to request_deletion(), which sets the grace period and cleans up matches, scores, and chat messages.

Parameters:
  • user (users.models.User) – The user requesting deletion.

  • confirmation_text (str) – Text submitted by the user to confirm intent. Must exactly match user.email.

Raises:

UserServiceError – If confirmation_text does not match user.email (HTTP 400).

Example:

>>> request_deletion(user, 'jdoe@ucla.edu')
users.services.cancel_deletion(user)[source]

Cancel a pending account deletion for an already-authenticated user.

Verifies that a deletion is actually pending before delegating to cancel_deletion().

Parameters:

user (users.models.User) – The authenticated user cancelling their deletion request.

Raises:

UserServiceError – If the account is not currently pending deletion (HTTP 400), or if the grace period has already elapsed (HTTP 409).

Example:

>>> cancel_deletion(request.user)
users.services.cancel_deletion_by_credentials(request, email, password)[source]

Authenticate by credentials and cancel a pending account deletion.

Intended for users who have been logged out but want to recover their account within the grace period. Authenticates via authenticate_user() then applies the same deletion-state checks as cancel_deletion().

Parameters:
  • request (django.http.HttpRequest) – The current HTTP request, forwarded to the auth backend.

  • email (str) – The user’s UCLA email address.

  • password (str) – The user’s plain-text password.

Returns:

The recovered User instance with deletion fields cleared.

Return type:

users.models.User

Raises:
  • Unauthorized – If credentials are invalid (HTTP 401).

  • Forbidden – If the email address is unverified (HTTP 403).

  • UserServiceError – If no deletion is pending (HTTP 400), or if the grace period has already elapsed (HTTP 409).

Example:

>>> user = cancel_deletion_by_credentials(request, 'jdoe@ucla.edu', 'secret123')

users.test_integration module

Integration tests for user authentication and profile management flows. Tests multiple components working together: models, services, views, database.

Run with: python manage.py test users.test_integration

class users.test_integration.AuthenticationFlowIntegrationTests(methodName='runTest')[source]

Bases: TestCase

Test complete authentication flows from registration to login

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_complete_registration_and_login_flow()[source]

Integration test: Register -> Auto-verify (test mode) -> Login -> Access profile Tests: services.py, models.py, views.py, authentication backend

test_unverified_user_cannot_login()[source]

Integration test: Register -> Don’t verify -> Try login -> Blocked Tests: Email verification enforcement across services and views

class users.test_integration.ProfileManagementIntegrationTests(methodName='runTest')[source]

Bases: TestCase

Test profile CRUD operations across multiple layers

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_complete_profile_update_flow()[source]

Integration test: Login -> Update profile -> Verify changes -> Check matching eligibility Tests: Authentication, profile updates, scoring triggers

test_security_role_change_blocked_via_api()[source]

Integration test: Attempt ADMIN escalation via API -> Verify blocked Tests: End-to-end security from API to database

test_security_role_change_blocked_via_api_2()[source]

Integration test: Attempt ADMIN escalation via API -> Verify blocked Tests: End-to-end security from API to database

class users.test_integration.AccountDeletionIntegrationTests(methodName='runTest')[source]

Bases: TestCase

Test complete account deletion workflows

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_deletion_request_and_cancellation_flow()[source]

Integration test: Request deletion -> Cancel -> Verify restored Tests: Deletion service, API, database state management

test_expired_account_cleanup_and_reregistration()[source]

Integration test: Delete account -> Expire -> Garbage collect -> Re-register Tests: Deletion lifecycle, cleanup service, registration validation

class users.test_integration.RoleChangeIntegrationTests(methodName='runTest')[source]

Bases: TestCase

Test role changes and their cascading effects

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_role_change_cleans_up_relationships()[source]

Integration test: Create match -> Change role -> Verify cleanup Tests: Matching system, role change service, database cascades

users.tests module

class users.tests.UserModelTests(methodName='runTest')[source]

Bases: TestCase

test_create_user()[source]

Verifies user creation

test_user_requires_email()[source]

Ensures email is required

test_user_requires_valid_ucla_email()[source]

Validates UCLA email domain

class users.tests.UserMatchingLogicTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_max_mentee_limit()[source]

Ensures a mentor cannot exceed the MAX_MENTEES limit

test_blacklisted_mentor_rejection()[source]

Ensures a mentor cannot add a mentee who blacklisted them

class users.tests.UserProfileSecurityTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_update_profile_ignores_protected_fields()[source]

Ensures users cannot overwrite protected fields like isMatched via profile updates

test_complete_survey_has_same_protections()[source]

complete_survey() should have same security as update_profile()

test_update_profile_allows_mentor_mentee_role_change()[source]

Users can switch between MENTOR and MENTEE

test_change_role_blocks_same_role()[source]

change_role() prevents no-op changes

class users.tests.AdminCreationTests(methodName='runTest')[source]

Bases: TestCase

Ensure ADMIN users can only be created via safe methods

test_admin_creation_via_create_superuser()[source]

Admins should be created via create_superuser

test_cannot_become_admin_via_profile_update()[source]

Check whether there is a loophole to become ADMIN

class users.tests.UserServiceTests(methodName='runTest')[source]

Bases: TestCase

test_register_user_service_success()[source]

Verifies the service creates a user and hashes the password

test_register_duplicate_email_raises_conflict()[source]

Ensures registering an existing email throws the correct Conflict error

class users.tests.RegistrationTests(methodName='runTest')[source]

Bases: TestCase

test_register_while_in_grace_period()[source]

Test that you can’t register during grace period

test_register_past_grace_period()[source]

Test that you can re-register after account expiration

class users.tests.UserAccountDeletionTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_request_deletion_flags_account()[source]

Ensures requesting deletion sets flags but doesn’t hard-delete immediately

test_cancel_deletion_restores_account()[source]

Ensures a user can cancel their deletion within the grace period

class users.tests.UserAPIClientTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_unauthenticated_user_access_denied()[source]

Ensures an unauthenticated browser cannot access the profile endpoint

test_authenticated_user_access_granted()[source]

Ensures a logged-in user can access their own profile data

class users.tests.UserRoleChangeTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_change_role_wipes_matches()[source]

Ensures that changing a role completely cleans up existing relationships

class users.tests.UserTokenExpirationTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_expired_verification_token_is_rejected()[source]

Ensures a token past its expiration time fails verification

class users.tests.HardDeletionTests(methodName='runTest')[source]

Bases: TestCase

setUp()[source]

Hook method for setting up the test fixture before exercising it.

test_only_expired_accounts_are_permanently_deleted()[source]

Ensures the hard-delete function respects the grace period

users.urls module

users.views module

users.views.register(request)[source]

Register a new user account.

Parses email, password, and optional role from the request body, delegates creation to services.register_user(), and returns the new account’s identifiers. A verification email is dispatched as a side-effect of the service call; the account cannot be used until the email address is confirmed.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with email, password, and optionally role.

Returns:

JSON response with userID, email, role, and a reminder to verify the account (HTTP 201), or an error payload (HTTP 400).

Return type:

django.http.JsonResponse

Raises:

UserServiceError – Caught internally; returns the error message and its associated HTTP status code.

Request body (JSON):

{
    "email": "jdoe@ucla.edu",
    "password": "secret123",
    "role": "MENTEE"          // optional, defaults to "MENTEE"
}

Success response (HTTP 201):

{
    "message": "Registration successful. Please check your email to verify account.",
    "userID": "550e8400-e29b-...",
    "email": "jdoe@ucla.edu",
    "role": "MENTEE",
    "note": "You must verify your email before logging in."
}
users.views.user_login(request)[source]

Authenticate a user and open a session.

Validates credentials via services.authenticate_user(), enforces email-verification and soft-deletion guards, then calls Django’s login() to establish a session. Also computes profile_complete based on whether the user has filled in their core profile fields.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with email and password.

Returns:

  • HTTP 200 with serialized user data and profile_complete flag on success.

  • HTTP 200 with deletion metadata if the account is pending deletion but still within the grace period.

  • HTTP 403 if the email address has not yet been verified.

  • HTTP 404 if the account’s grace period has expired and it has been purged.

  • HTTP 400 for missing fields or unexpected errors.

Return type:

django.http.JsonResponse

Raises:

UserServiceError – Caught internally; returns the error message and its associated HTTP status code.

Request body (JSON):

{
    "email": "jdoe@ucla.edu",
    "password": "secret123"
}

Success response (HTTP 200):

{
    "message": "Login successful",
    "profile_complete": true,
    // ...serialized user fields
}
users.views.user_logout(request)[source]

Terminate the current user session.

Calls Django’s logout() to flush the session, regardless of whether the caller is authenticated.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request.

Returns:

JSON confirmation message (HTTP 200).

Return type:

django.http.JsonResponse

users.views.get_current_user(request)[source]

Return the profile of the currently authenticated user.

Computes profile_complete based on whether core profile fields have been populated. Returns deletion metadata instead of the standard payload when the account is pending deletion.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP GET request. Must have an authenticated session.

Returns:

  • HTTP 200 with serialized user data and profile_complete on success.

  • HTTP 200 with deletion metadata if the account is pending deletion.

  • HTTP 401 if the request is unauthenticated.

Return type:

django.http.JsonResponse

Success response (HTTP 200):

{
    "profile_complete": true,
    // ...serialized user fields
}
users.views.update_profile(request)[source]

Update the authenticated user’s profile fields.

Accepts any subset of the fields permitted by User.SURVEY_FIELDS. Protected fields are silently ignored by the model layer.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP PUT request containing a JSON body with one or more profile field key-value pairs.

Returns:

  • HTTP 200 with a success message on update.

  • HTTP 400 if the request body is not valid JSON.

  • HTTP 401 if the request is unauthenticated.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "firstName": "Jane",
    "year": 2,
    "major": ["Computer Science"]
}
users.views.complete_survey(request)[source]

Submit the authenticated user’s initial onboarding survey.

Delegates to services.complete_survey(), which applies the same field-whitelist rules as update_profile().

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with survey field key-value pairs.

Returns:

  • HTTP 200 with a success message on completion.

  • HTTP 400 for unexpected errors.

  • HTTP 401 if the request is unauthenticated.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "firstName": "Jane",
    "lastName": "Doe",
    "year": 1,
    "major": ["Biology"],
    "international": false
}
users.views.request_account_deletion(request)[source]

Initiate a soft-delete of the authenticated user’s account.

Requires the caller to supply a confirmation string in the request body. The account is not immediately removed; a grace period is applied during which the user may cancel via cancel_account_deletion().

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with a confirmation string.

Returns:

  • HTTP 200 with permanent_deletion_date on success.

  • HTTP 400 for unexpected errors.

  • HTTP 401 if the request is unauthenticated.

  • Appropriate error status from UserServiceError if the confirmation text is invalid.

Return type:

django.http.JsonResponse

Raises:

UserServiceError – Caught internally; returns the error message and its associated HTTP status code.

Request body (JSON):

{
    "confirmation": "delete my account"
}

Success response (HTTP 200):

{
    "message": "Account deletion requested",
    "permanent_deletion_date": "2024-01-01T12:00:00+00:00",
    "note": "Your account will be permanently deleted in 1 hour. You can cancel before then."
}
users.views.cancel_account_deletion(request)[source]

Cancel a pending account deletion for the currently authenticated user.

Delegates to services.cancel_deletion(). The request will fail if the grace period has already elapsed. Use cancel_account_deletion_public() instead when the caller does not have an active session.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request. No body is required.

Returns:

  • HTTP 200 with a success message on cancellation.

  • HTTP 401 if the request is unauthenticated.

  • Appropriate error status from UserServiceError if the grace period has expired.

Return type:

django.http.JsonResponse

Raises:

UserServiceError – Caught internally; returns the error message and its associated HTTP status code.

users.views.cancel_account_deletion_public(request)[source]

Cancel a pending account deletion without an active session.

Accepts credentials in the request body and authenticates the user before delegating to services.cancel_deletion_by_credentials(). Intended for users who have been logged out but still want to recover their account within the grace period.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with email and password.

Returns:

  • HTTP 200 with email and is_active on success.

  • HTTP 400 if email or password is missing, or for unexpected errors.

  • Appropriate error status from UserServiceError on auth failure or expired grace period.

Return type:

django.http.JsonResponse

Raises:

UserServiceError – Caught internally; returns the error message and its associated HTTP status code.

Request body (JSON):

{
    "email": "jdoe@ucla.edu",
    "password": "secret123"
}
users.views.verify_email(request, token)[source]

Verify a user’s email address using a one-time token.

Looks up the User whose email_verification_token matches the supplied token path parameter, then delegates to User.verify_email(). Returns an appropriate response if the account is already verified or if the token has expired.

Parameters:
  • request (django.http.HttpRequest) – The incoming HTTP GET request.

  • token (str) – The UUID verification token embedded in the confirmation link.

Returns:

  • HTTP 200 with a success message and email on successful verification.

  • HTTP 200 with 'Email already verified' if the account was already confirmed.

  • HTTP 400 if the token is invalid or has expired.

Return type:

django.http.JsonResponse

Success response (HTTP 200):

{
    "message": "Email verified successfully. You can now log in.",
    "email": "jdoe@ucla.edu"
}
users.views.resend_verification(request)[source]

Resend the email verification link to a user.

Looks up the account by email and issues a new verification token via User.generate_verification_token(). To prevent email enumeration, the response is identical whether or not the address exists in the system.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with email.

Returns:

  • HTTP 200 with 'Verification email sent' on dispatch.

  • HTTP 200 with a non-committal message if the email is not found (prevents enumeration).

  • HTTP 200 with 'Email already verified' if the account is already confirmed.

  • HTTP 400 if email is missing or an unexpected error occurs.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "email": "jdoe@ucla.edu"
}
users.views.change_role(request)[source]

Switch the authenticated user’s role between MENTEE and MENTOR.

All existing matches, compatibility scores, and chat messages are cleared before the role transition is applied (delegated to User.change_role()).

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with new_role.

Returns:

  • HTTP 200 with a success message and serialized user data on success.

  • HTTP 400 if new_role is missing, the JSON is invalid, or the role transition is not permitted.

  • HTTP 401 if the request is unauthenticated.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "new_role": "MENTOR"
}
users.views.upload_profile_picture(request)[source]

Upload and store a profile picture for the authenticated user.

Sends the supplied file to Cloudinary with a 400×400 face-crop transformation and returns the resulting secure URL. The caller is responsible for subsequently persisting the URL to the user’s profile via update_profile().

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request with a multipart/form-data body containing a file field.

Returns:

  • HTTP 200 with url pointing to the uploaded image on success.

  • HTTP 400 if no file is provided.

  • HTTP 401 if the request is unauthenticated.

Return type:

django.http.JsonResponse

Success response (HTTP 200):

{
    "url": "https://res.cloudinary.com/.../profile_pictures/abc123.jpg"
}
users.views.request_password_reset(request)[source]

Dispatch a password reset email to the specified address.

Generates a one-time token via User.generate_password_reset_token() and sends it via send_password_reset_email(). To prevent email enumeration, the response is identical whether or not the address exists.

Parameters:

request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with email.

Returns:

  • HTTP 200 with a non-committal message regardless of whether the email exists (prevents enumeration).

  • HTTP 400 if email is missing or an unexpected error occurs.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "email": "jdoe@ucla.edu"
}
users.views.reset_password(request, token)[source]

Reset a user’s password using a one-time token.

Looks up the User whose password_reset_token matches the supplied token path parameter, then delegates to User.reset_password_with_token().

Parameters:
  • request (django.http.HttpRequest) – The incoming HTTP POST request containing a JSON body with new_password.

  • token (str) – The UUID password reset token embedded in the reset link.

Returns:

  • HTTP 200 with a success message on reset.

  • HTTP 400 if new_password is missing, the token is invalid, the token has expired, or an unexpected error occurs.

Return type:

django.http.JsonResponse

Request body (JSON):

{
    "new_password": "newSecurePass!"
}

Success response (HTTP 200):

{
    "message": "Password reset successfully. You can now log in."
}

Module contents

show-inheritance:

undoc-members: