Source code for accounts.models

# noinspection PyProtectedMember
from django.contrib.auth.models import AbstractUser, _user_has_perm
from django.db.models import (Model, BooleanField, CharField, IntegerField, BigIntegerField, PositiveIntegerField, Q,
                              TextField, DateField, DateTimeField, OneToOneField, ManyToManyField, ImageField, CASCADE,
                              SET_NULL, signals)
from django.core.validators import MinValueValidator,MaxValueValidator
from django.utils import timezone
from multiselectfield import MultiSelectField
from django.conf import settings
from six import python_2_unicode_compatible
from django.dispatch import receiver

from data.storage import OverwriteStorage
from events.models import Organization
from meetings.models import MeetingType

import os

# from django_custom_user_migration.models import AbstractUser

carrier_choices = (
    ('', 'Opt-out'),
    ('txt.att.net', 'AT&T'),
    ('myboostmobile.com', 'Boost Mobile'),
    ('mms.cricketwireless.net', 'Cricket'),
    ('msg.fi.google.com', 'Google Fi'),
    ('mymetropcs.com', 'Metro PCS'),
    ('mmst5.tracfone.com', 'Simple Mobile'),
    ('messaging.sprintpcs.com', 'Sprint'),
    ('tmomail.net', 'T-Mobile'),
    ('vtext.com', 'Verizon'),
    ('vmobl.com', 'Virgin Mobile'),
    ('vmobile.ca', 'Virgin Mobile Canada'),
    ('vtext.com', 'Xfinity Mobile')
)


[docs]@python_2_unicode_compatible class User(AbstractUser): """Extended User Class"""
[docs] def save(self, *args, **kwargs): # gives an email from the username when first created (ie. via CAS) if not self.pk and not self.email: self.email = "%s@wpi.edu" % self.username super(User, self).save(*args, **kwargs)
wpibox = IntegerField(null=True, blank=True, verbose_name="WPI Box Number") phone = CharField(max_length=24, null=True, blank=True, verbose_name="Phone Number") carrier = CharField(choices=carrier_choices, max_length=25, verbose_name="Cellular Carrier", help_text="By selecting your cellular carrier you consent to receiving text messages from LNL", null=True, blank=True, default='') addr = TextField(null=True, blank=True, verbose_name="Address / Office Location") mdc = CharField(max_length=32, null=True, blank=True, verbose_name="MDC") nickname = CharField(max_length=32, null=True, blank=True, verbose_name="Nickname") student_id = PositiveIntegerField(null=True, blank=True, verbose_name="Student ID") class_year = PositiveIntegerField(null=True, blank=True, validators=[MinValueValidator(1962), MaxValueValidator(timezone.now().year + 6)]) locked = BooleanField(default=False) away_exp = DateField(verbose_name="Away Status Expiration", null=True, blank=True) onboarded = BooleanField(default=False, verbose_name="Onboarding Complete") pronouns = CharField(max_length=32, null=True, blank=True, verbose_name="Pronouns") def __str__(self): nick = '"%s" ' % self.nickname if self.nickname else "" if self.first_name or self.last_name: return self.first_name + " " + nick + self.last_name return "[%s]" % self.username
[docs] def has_perm(self, perm, obj=None): """ Returns True if the user has the specified permission. This method queries all available auth backends, but returns immediately if any backend returns True. Thus, a user who has permission from a single auth backend is assumed to have permission in general. If an object is provided, permissions for this specific object are checked. This differs from the default in that superusers, while still having every permission, will be allowed after the logic has executed. This helps with typos in permission strings. """ has_perm = _user_has_perm(self, perm, obj) # Active superusers have all permissions. if self.is_active and self.is_superuser: return True else: return has_perm
@property def name(self): """User's full name""" return self.first_name + " " + self.last_name @property def is_lnl(self): """Is an LNL member""" return self.groups.filter(Q(name="Alumni") | Q(name="Active") | Q(name="Officer") | Q(name="Associate") | Q( name="Away") | Q(name="Inactive")).exists() @property def is_complete(self): """ Returns false if the user's profile is incomplete. The user will be constantly reminded to complete their profile. """ # if this returns false, the user will be constantly reminded to update their profile return self.first_name and self.last_name and self.email and (not self.is_lnl or self.class_year) @property def group_str(self): """Groups the user belongs to""" groups = [g.name for g in self.groups.all()] out_str = "" if "Alumni" in groups: out_str += 'Alum ' if "Officer" in groups: out_str += 'Officer' elif "Active" in groups: out_str += 'Active' elif "Associate" in groups: out_str += 'Associate' elif "Away" in groups: out_str += 'Away' elif "Inactive" in groups: out_str += 'Inactive' else: out_str += "Unclassified" return out_str @property def owns(self): """Organizations the user owns""" return ', '.join(map(str, self.orgowner.all())) @property def orgs(self): """Organizations the user belongs to""" return ', '.join(map(str, self.orgusers.all())) @property def all_orgs(self): """All organizations the user is associated with""" return Organization.objects.complex_filter( Q(user_in_charge=self) | Q(associated_users=self) ).distinct() @property def mdc_name(self): max_chars = 12 clean_first, clean_last = "", "" # assume that Motorola can handle practically nothing. Yes, ugly, but I don't wanna regex 1000's of times for char in self.first_name.upper().strip(): if ord(char) == ord(' ') or ord(char) == ord('-') \ or ord('0') <= ord(char) <= ord('9') or ord('A') <= ord(char) <= ord('Z'): clean_first += char for char in self.last_name.upper().strip(): if ord(char) == ord(' ') or ord(char) == ord('-') \ or ord('0') <= ord(char) <= ord('9') or ord('A') <= ord(char) <= ord('Z'): clean_last += char outstr = clean_last[:max_chars - 2] + "," # leave room for at least an initial outstr += clean_first[:max_chars - len(outstr)] # fill whatever's left with the first name return outstr class Meta: ordering = 'last_name', 'first_name', 'class_year' permissions = ( ('change_membership', 'Change the group membership of a user'), ('edit_mdc', 'Change the MDC of a user'), ('view_member', 'View LNL members'), )
[docs]def path_and_rename(instance, filename): """ Determine path for storing officer headshots. Will rename with officer's username. :param instance: A ProfilePhoto instance :param filename: The original name of the uploaded file :returns: New path to save file to """ upload_to = 'officers' ext = filename.split('.')[-1] if instance.officer.get_username(): filename = "{}.{}".format(instance.officer.get_username(), ext) return os.path.join(upload_to, filename)
[docs]@python_2_unicode_compatible class ProfilePhoto(Model): """ Officer profile photo """ officer = OneToOneField(User, on_delete=CASCADE, related_name="img") img = ImageField(upload_to=path_and_rename, storage=OverwriteStorage(), verbose_name="Image") def __str__(self): return self.officer.name
[docs]@receiver(signals.post_delete, sender=ProfilePhoto) def officer_img_cleanup(sender, instance, **kwargs): """ When an instance of ProfilePhoto is deleted, delete the respective files as well. :param instance: A ProfilePhoto instance """ instance.img.delete(False)
[docs]class Officer(Model): """ Represents an Officer position """ user = OneToOneField(User, on_delete=SET_NULL, blank=True, null=True, related_name="exec_position") title = CharField(max_length=60, verbose_name="Officer Position") img = OneToOneField(ProfilePhoto, on_delete=SET_NULL, blank=True, null=True, related_name="officer_img") def __str__(self): return self.title
[docs]class PhoneVerificationCode(Model): """Used for temporarily saving the last code sent to a user to verify their phone number""" user = OneToOneField(settings.AUTH_USER_MODEL, on_delete=CASCADE, related_name="verification_codes") code = BigIntegerField() timestamp = DateTimeField(auto_now_add=True)
event_fields = [ ('event_name', 'Event name'), ('description', 'Description'), ('location', 'Location'), ('contact', 'Contact'), ('lnl_contact', 'LNL contact'), ('billing_org', 'Billing org'), ('datetime_setup_complete', 'Datetime setup complete'), ('datetime_start', 'Datetime start'), ('datetime_end', 'Datetime end'), ('internal_notes', 'Internal notes'), ('billed_in_bulk', 'Billed in bulk'), ('org', 'Client') ]
[docs]class UserPreferences(Model): """ User-specific settings """ user = OneToOneField(settings.AUTH_USER_MODEL, on_delete=CASCADE, related_name="preferences") theme = CharField(choices=(("default", "Default"),), default="default", max_length=12) rt_token = CharField(blank=True, null=True, verbose_name="RT Auth Token", max_length=256) # Communication Preferences cc_add_subscriptions = MultiSelectField(choices=(('email', 'Email'), ('slack', 'Slack Notification')), default='email', blank=True, null=True) cc_report_reminders = CharField(choices=(('email', 'Email'), ('slack', 'Slack Notification'), ('all', 'Both')), default='email', max_length=12) event_edited_notification_methods = CharField( choices=(('email', 'Email'), ('slack', 'Slack Notification'), ('all', 'Both')), default='email', max_length=12 ) event_edited_field_subscriptions = MultiSelectField( choices=event_fields, default=['location', 'datetime_setup_complete', 'datetime_start', 'datetime_end'] ) ignore_user_action = BooleanField( default=False, help_text="Uncheck this to ignore notifications for actions triggered by the user" ) meeting_invites = BooleanField( default=False, help_text="Opt-in to receiving calendar invites for meetings" ) meeting_invite_subscriptions = ManyToManyField(MeetingType, blank=True, related_name="invite_subscriptions")