class BadgeUser(BaseVersionedEntity, AbstractUser, cachemodel.CacheModel): """ A full-featured user model that can be an Earner, Issuer, or Consumer of Open Badges """ entity_class_name = 'BadgeUser' USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] badgrapp = models.ForeignKey('mainsite.BadgrApp', blank=True, null=True, default=None) marketing_opt_in = models.BooleanField(default=False) objects = BadgeUserManager() class Meta: verbose_name = _('badge user') verbose_name_plural = _('badge users') db_table = 'users' def __unicode__(self): return u"{} <{}>".format(self.get_full_name(), self.email) def get_full_name(self): return u"%s %s" % (self.first_name, self.last_name) def email_user(self, subject, message, from_email=None, **kwargs): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.primary_email], **kwargs) def publish(self): super(BadgeUser, self).publish() self.publish_by('username') def delete(self, *args, **kwargs): super(BadgeUser, self).delete(*args, **kwargs) self.publish_delete('username') @cachemodel.cached_method(auto_publish=True) def cached_emails(self): return CachedEmailAddress.objects.filter(user=self) @cachemodel.cached_method(auto_publish=True) def cached_backpackcollections(self): return BackpackCollection.objects.filter(created_by=self) @property def email_items(self): return self.cached_emails() @email_items.setter def email_items(self, value): """ Update this users EmailAddress from a list of BadgeUserEmailSerializerV2 data :param value: list(BadgeUserEmailSerializerV2) :return: None """ if len(value) < 1: raise ValidationError("Must have at least 1 email") new_email_idx = {d['email']: d for d in value} primary_count = sum(1 if d.get('primary', False) else 0 for d in value) if primary_count != 1: raise ValidationError("Must have exactly 1 primary email") with transaction.atomic(): # add or update existing items for email_data in value: primary = email_data.get('primary', False) emailaddress, created = CachedEmailAddress.cached.get_or_create( email=email_data['email'], defaults={ 'user': self, 'primary': primary }) if created: # new email address send a confirmation emailaddress.send_confirmation() else: if emailaddress.user_id == self.id: # existing email address owned by user emailaddress.primary = primary emailaddress.save() elif not emailaddress.verified: # existing unverified email address, handover to this user emailaddress.user = self emailaddress.primary = primary emailaddress.save() emailaddress.send_confirmation() else: # existing email address used by someone else raise ValidationError( "Email '{}' may already be in use".format( email_data.get('email'))) # remove old items for emailaddress in self.email_items: if emailaddress.email not in new_email_idx: emailaddress.delete() def cached_email_variants(self): return chain.from_iterable(email.cached_variants() for email in self.cached_emails()) def can_add_variant(self, email): try: canonical_email = CachedEmailAddress.objects.get(email=email, user=self, verified=True) except CachedEmailAddress.DoesNotExist: return False if email != canonical_email.email \ and email not in [e.email for e in canonical_email.cached_variants()] \ and EmailAddressVariant(email=email, canonical_email=canonical_email).is_valid(): return True return False @property def primary_email(self): primaries = filter(lambda e: e.primary, self.cached_emails()) if len(primaries) > 0: return primaries[0].email return self.email @property def verified_emails(self): return filter(lambda e: e.verified, self.cached_emails()) @property def verified(self): if self.is_superuser: return True if len(self.verified_emails) > 0: return True return False @property def all_recipient_identifiers(self): return [e.email for e in self.cached_emails() if e.verified ] + [e.email for e in self.cached_email_variants()] @cachemodel.cached_method(auto_publish=True) def cached_issuers(self): return Issuer.objects.filter(staff__id=self.id).distinct() @property def peers(self): """ a BadgeUser is a Peer of another BadgeUser if they appear in an IssuerStaff together """ return set( chain(*[[s.cached_user for s in i.cached_issuerstaff()] for i in self.cached_issuers()])) def cached_badgeclasses(self): return chain.from_iterable(issuer.cached_badgeclasses() for issuer in self.cached_issuers()) @cachemodel.cached_method(auto_publish=True) def cached_badgeinstances(self): return BadgeInstance.objects.filter( recipient_identifier__in=self.all_recipient_identifiers) @cachemodel.cached_method(auto_publish=True) def cached_externaltools(self): return [ a.cached_externaltool for a in self.externaltooluseractivation_set.filter(is_active=True) ] @cachemodel.cached_method(auto_publish=True) def cached_token(self): user_token, created = \ Token.objects.get_or_create(user=self) return user_token.key @cachemodel.cached_method(auto_publish=True) def cached_agreed_terms_version(self): try: return self.termsagreement_set.all().order_by('-terms_version')[0] except IndexError: pass return None @property def agreed_terms_version(self): v = self.cached_agreed_terms_version() if v is None: return 0 return v.terms_version @agreed_terms_version.setter def agreed_terms_version(self, value): try: value = int(value) except ValueError as e: return if value > self.agreed_terms_version: if TermsVersion.active_objects.filter(version=value).exists(): if not self.pk: self.save() self.termsagreement_set.get_or_create( terms_version=value, defaults=dict(agreed=True)) def replace_token(self): Token.objects.filter(user=self).delete() # user_token, created = \ # Token.objects.get_or_create(user=self) self.save() return self.cached_token() def save(self, *args, **kwargs): if not self.username: # md5 hash the email and then encode as base64 to take up only 25 characters hashed = md5(self.email + ''.join( random.choice(string.lowercase) for i in range(64))).digest().encode( 'base64')[: -1] # strip last character because its a newline self.username = "******".format(hashed[:25]) if getattr(settings, 'BADGEUSER_SKIP_LAST_LOGIN_TIME', True): # skip saving last_login to the database if 'update_fields' in kwargs and kwargs[ 'update_fields'] is not None and 'last_login' in kwargs[ 'update_fields']: kwargs['update_fields'].remove('last_login') if len(kwargs['update_fields']) < 1: # nothing to do, abort so we dont call .publish() return return super(BadgeUser, self).save(*args, **kwargs)
class BadgeUser(BaseVersionedEntity, AbstractUser, cachemodel.CacheModel): """ A full-featured user model that can be an Earner, Issuer, or Consumer of Open Badges """ entity_class_name = 'BadgeUser' badgrapp = models.ForeignKey('mainsite.BadgrApp', on_delete=models.SET_NULL, blank=True, null=True, default=None) faculty = models.ManyToManyField('institution.Faculty', blank=True) institution = models.ForeignKey('institution.Institution', on_delete=models.SET_NULL, blank=True, null=True, default=None) is_staff = models.BooleanField( _('Backend-staff member'), default=False, help_text=_('Designates whether the user can log into this admin site.'), ) # canvas LTI id lti_id = models.CharField(unique=True, max_length=50, default=None, null=True, blank=True, help_text='LTI user id, unique per user') marketing_opt_in = models.BooleanField(default=False) objects = BadgeUserManager() class Meta: verbose_name = _('badge user') verbose_name_plural = _('badge users') db_table = 'users' permissions=(('view_issuer_tab', 'User can view Issuer tab in front end'), ('view_management_tab', 'User can view Management dashboard'), ('has_faculty_scope', 'User has faculty scope'), ('has_institution_scope', 'User has institution scope'), ('ui_issuer_add', 'User can add issuer in front end'), ) def __unicode__(self): return "{} <{}>".format(self.get_full_name(), self.email) def within_scope(self, object): if object: if self.has_perm('badgeuser.has_institution_scope'): return object.institution == self.institution if self.has_perm('badgeuser.has_faculty_scope'): if object.faculty.__class__.__name__ == 'ManyRelatedManager': return bool(set(object.faculty.all()).intersection(set(self.faculty.all()))) else: return object.faculty in self.faculty.all() return False def get_badgr_app(self): if self.badgrapp: return self.badgrapp else: return BadgrApp.objects.all().first() def get_full_name(self): return "%s %s" % (self.first_name, self.last_name) def gains_permission(self, permission_codename, model): content_type = ContentType.objects.get_for_model(model) permission = Permission.objects.get(codename=permission_codename, content_type=content_type) self.user_permissions.add(permission) # you still need to reload user from db to refresh permission cache if you want effect to be immediate def loses_permission(self, permission_codename, model): content_type = ContentType.objects.get_for_model(model) permission = Permission.objects.get(codename=permission_codename, content_type=content_type) self.user_permissions.remove(permission) # you still need to reload user from db to refresh permission cache if you want effect to be immediate def email_user(self, subject, message, from_email=None, attachments=None, **kwargs): """ Sends an email to this User. """ try: EmailBlacklist.objects.get(email=self.primary_email) except EmailBlacklist.DoesNotExist: # Allow sending, as this email is not blacklisted. if not attachments: send_mail(subject, message, from_email, [self.primary_email], **kwargs) else: from django.core.mail import EmailMessage email = EmailMessage(subject=subject, body=message, from_email=from_email, to=[self.primary_email], attachments=attachments) email.send() else: return # TODO: Report email non-delivery somewhere. def publish(self): super(BadgeUser, self).publish() self.publish_by('username') def delete(self, *args, **kwargs): super(BadgeUser, self).delete(*args, **kwargs) self.publish_delete('username') # @cachemodel.cached_method(auto_publish=True) # turned it off, because if user logs in for FIRST time, this caching will result in the user having no verified emails. # This results in api calls responding with a 403 after the failure of the AuthenticatedWithVerifiedEmail permission check. # Which will logout the user automatically with the error: Token expired. def cached_emails(self): return CachedEmailAddress.objects.filter(user=self) @cachemodel.cached_method(auto_publish=True) def cached_backpackcollections(self): return BackpackCollection.objects.filter(created_by=self) @property def email_items(self): return self.cached_emails() @property def may_sign_assertions(self): return self.has_perm('signing.may_sign_assertions') @property def current_symmetric_key(self): return self.symmetrickey_set.get(current=True) @email_items.setter def email_items(self, value): """ Update this users EmailAddress from a list of BadgeUserEmailSerializerV2 data :param value: list(BadgeUserEmailSerializerV2) :return: None """ if len(value) < 1: raise ValidationError("Must have at least 1 email") new_email_idx = {d['email']: d for d in value} primary_count = sum(1 if d.get('primary', False) else 0 for d in value) if primary_count != 1: raise ValidationError("Must have exactly 1 primary email") with transaction.atomic(): # add or update existing items for email_data in value: primary = email_data.get('primary', False) emailaddress, created = CachedEmailAddress.cached.get_or_create( email=email_data['email'], defaults={ 'user': self, 'primary': primary }) if created: # new email address send a confirmation emailaddress.send_confirmation() else: if emailaddress.user_id == self.id: # existing email address owned by user emailaddress.primary = primary emailaddress.save() elif not emailaddress.verified: # existing unverified email address, handover to this user emailaddress.user = self emailaddress.primary = primary emailaddress.save() emailaddress.send_confirmation() else: # existing email address used by someone else raise ValidationError("Email '{}' may already be in use".format(email_data.get('email'))) # remove old items for emailaddress in self.email_items: if emailaddress.email not in new_email_idx: emailaddress.delete() def cached_email_variants(self): return chain.from_iterable(email.cached_variants() for email in self.cached_emails()) def can_add_variant(self, email): try: canonical_email = CachedEmailAddress.objects.get(email=email, user=self, verified=True) except CachedEmailAddress.DoesNotExist: return False if email != canonical_email.email \ and email not in [e.email for e in canonical_email.cached_variants()] \ and EmailAddressVariant(email=email, canonical_email=canonical_email).is_valid(): return True return False @property def primary_email(self): primaries = [e for e in self.cached_emails() if e.primary] if len(primaries) > 0: return primaries[0].email return self.email @property def verified_emails(self): return [e for e in self.cached_emails() if e.verified] @property def verified(self): if self.is_superuser: return True if len(self.verified_emails) > 0: return True return False @property def all_recipient_identifiers(self): return [self.get_recipient_identifier()] # return [e.email for e in self.cached_emails() if e.verified] + [e.email for e in self.cached_email_variants()] @property def highest_group(self): groups = list(self.groups.filter(entity_rank__rank__gte=0)) groups.sort(key=lambda x: x.entity_rank.rank) if groups: return groups[0] else: return None def get_recipient_identifier(self): from allauth.socialaccount.models import SocialAccount try: account = SocialAccount.objects.get(user=self.pk) return account.extra_data['sub'] except SocialAccount.DoesNotExist: return None def get_social_account(self): from allauth.socialaccount.models import SocialAccount try: account = SocialAccount.objects.get(user=self.pk) return account except SocialAccount.DoesNotExist: return None def has_edu_id_social_account(self): social_account = self.get_social_account() return social_account.provider == 'edu_id' or social_account.provider == 'surfconext_ala' def has_surf_conext_social_account(self): social_account = self.get_social_account() return social_account.provider == 'surf_conext' def staff_memberships(self): """ Returns all staff memberships """ return Issuer.objects.filter(staff__id=self.id) def is_email_verified(self, email): if email in [e.email for e in self.verified_emails]: return True try: app_infos = ApplicationInfo.objects.filter(application__user=self) if any(app_info.trust_email_verification for app_info in app_infos): return True except ApplicationInfo.DoesNotExist: return False return False def get_assertions_ready_for_signing(self): assertion_timestamps = AssertionTimeStamp.objects.filter(signer=self).exclude(proof='') return [ts.badge_instance for ts in assertion_timestamps if ts.badge_instance.signature == None] @cachemodel.cached_method(auto_publish=True) def cached_issuers(self): ''' Creates a list of issuers belonging to the user's scope ''' queryset = Issuer.objects.filter(staff__id=self.id) # personal issuers if self.has_perm('badgeuser.has_institution_scope'): institution = self.institution if institution: queryset = queryset | Issuer.objects.filter(faculty__institution=institution) # add insitution issuers elif self.has_perm('badgeuser.has_faculty_scope'): faculties = self.faculty.all() if faculties: queryset = queryset | Issuer.objects.filter(faculty__in=self.faculty.all()) # add faculty issuers return queryset.distinct() @property def peers(self): """ a BadgeUser is a Peer of another BadgeUser if they appear in an IssuerStaff together """ return set(chain(*[[s.cached_user for s in i.cached_issuerstaff()] for i in self.cached_issuers()])) def cached_badgeclasses(self): return chain.from_iterable(issuer.cached_badgeclasses() for issuer in self.cached_issuers()) @cachemodel.cached_method(auto_publish=True) def cached_badgeinstances(self): # return BadgeInstance.objects.filter(recipient_identifier__in=self.all_recipient_identifiers) return BadgeInstance.objects.filter(recipient_identifier=self.get_recipient_identifier()) @cachemodel.cached_method(auto_publish=True) def cached_externaltools(self): return [a.cached_externaltool for a in self.externaltooluseractivation_set.filter(is_active=True)] @cachemodel.cached_method(auto_publish=True) def cached_token(self): user_token, created = \ Token.objects.get_or_create(user=self) return user_token.key @cachemodel.cached_method(auto_publish=True) def cached_agreed_terms_version(self): try: return self.termsagreement_set.all().filter(valid=True).order_by('-terms_version')[0] except IndexError: pass return None @property def agreed_terms_version(self): v = self.cached_agreed_terms_version() if v is None: return 0 return v.terms_version @agreed_terms_version.setter def agreed_terms_version(self, value): try: value = int(value) except ValueError as e: return if value > self.agreed_terms_version: if TermsVersion.active_objects.filter(version=value).exists(): if not self.pk: self.save() self.termsagreement_set.get_or_create(terms_version=value, defaults=dict(agreed=True)) def replace_token(self): Token.objects.filter(user=self).delete() # user_token, created = \ # Token.objects.get_or_create(user=self) self.save() return self.cached_token() def may_enroll(self, badge_class): """ Checks to see if user may enroll no enrollments: May enroll any not awarded assertions: May not enroll Any awarded and not revoked: May not enroll All revoked: May enroll """ social_account = self.get_social_account() if social_account.provider == 'edu_id' or social_account.provider == 'surfconext_ala': enrollments = StudentsEnrolled.objects.filter(user=social_account.user, badge_class_id=badge_class.pk) if not enrollments: return True # no enrollments else: for enrollment in enrollments: if not bool(enrollment.badge_instance): # has never been awarded return False else: #has been awarded if not enrollment.assertion_is_revoked(): return False return True # all have been awarded and revoked else: # no eduID return False def save(self, *args, **kwargs): if not self.username: # md5 hash the email and then encode as base64 to take up only 25 characters hashed = md5(self.email + ''.join(random.choice(string.lowercase) for i in range(64))).digest().encode('base64')[:-1] # strip last character because its a newline self.username = "******".format(hashed[:25]) if getattr(settings, 'BADGEUSER_SKIP_LAST_LOGIN_TIME', True): # skip saving last_login to the database if 'update_fields' in kwargs and kwargs['update_fields'] is not None and 'last_login' in kwargs['update_fields']: kwargs['update_fields'].remove('last_login') if len(kwargs['update_fields']) < 1: # nothing to do, abort so we dont call .publish() return return super(BadgeUser, self).save(*args, **kwargs)
class BadgeUser(BaseVersionedEntity, AbstractUser, cachemodel.CacheModel): """ A full-featured user model that can be an Earner, Issuer, or Consumer of Open Badges """ entity_class_name = 'BadgeUser' USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] badgrapp = models.ForeignKey('mainsite.BadgrApp', blank=True, null=True, default=None, on_delete=models.SET_NULL) marketing_opt_in = models.BooleanField(default=False) objects = BadgeUserManager() class Meta: verbose_name = _('badge user') verbose_name_plural = _('badge users') db_table = 'users' def __str__(self): primary_identifier = self.email or next((e for e in self.all_verified_recipient_identifiers), '') return "{} ({})".format(self.get_full_name(), primary_identifier) def get_full_name(self): return "%s %s" % (self.first_name, self.last_name) def email_user(self, subject, message, from_email=None, **kwargs): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.primary_email], **kwargs) def publish(self): super(BadgeUser, self).publish() self.publish_by('username') def delete(self, *args, **kwargs): cached_emails = self.cached_emails() if cached_emails.exists(): for email in cached_emails: email.delete() super(BadgeUser, self).delete(*args, **kwargs) self.publish_delete('username') @cachemodel.cached_method(auto_publish=True) def cached_verified_urls(self): return [ r.identifier for r in self.userrecipientidentifier_set.filter( verified=True, type=UserRecipientIdentifier.IDENTIFIER_TYPE_URL)] @cachemodel.cached_method(auto_publish=True) def cached_verified_phone_numbers(self): return [ r.identifier for r in self.userrecipientidentifier_set.filter( verified=True, type=UserRecipientIdentifier.IDENTIFIER_TYPE_TELEPHONE)] @cachemodel.cached_method(auto_publish=True) def cached_emails(self): return CachedEmailAddress.objects.filter(user=self) @cachemodel.cached_method(auto_publish=True) def cached_backpackcollections(self): return BackpackCollection.objects.filter(created_by=self) @property def email_items(self): return self.cached_emails() @email_items.setter def email_items(self, value): """ Update this users EmailAddress from a list of BadgeUserEmailSerializerV2 data :param value: list(BadgeUserEmailSerializerV2) :return: None """ return self.set_email_items(value) def set_email_items(self, value, send_confirmations=True, allow_verify=False, is_privileged_user=False): """ If value is empty and the user is privileged, assume they meant to remove the users email and allow it. E.g. They are preforming some sort of ELT use case """ if is_privileged_user and len(value) == 0: for email_address in self.email_items: email_address.delete() self.email = '' self.save() else: if len(value) < 1: raise ValidationError("Must have at least 1 email") new_email_idx = {d['email']: d for d in value} primary_count = sum(1 if d.get('primary', False) else 0 for d in value) if primary_count != 1: raise ValidationError("Must have exactly 1 primary email") requested_primary = [d for d in value if d.get('primary', False)][0] with transaction.atomic(): # add or update existing items for email_data in value: primary = email_data.get('primary', False) verified = email_data.get('verified', False) emailaddress, created = CachedEmailAddress.cached.get_or_create( email=email_data['email'], defaults={ 'user': self, 'primary': primary }) if not created: dirty = False if emailaddress.user_id == self.id: # existing email address owned by user emailaddress.primary = primary dirty = True elif not emailaddress.verified: # existing unverified email address, handover to this user emailaddress.user = self emailaddress.primary = primary emailaddress.save() # in this case, don't mark as dirty emailaddress.send_confirmation() else: # existing email address used by someone else raise ValidationError("Email '{}' may already be in use".format(email_data.get('email'))) if allow_verify and verified != emailaddress.verified: emailaddress.verified = verified dirty = True if dirty: emailaddress.save() else: # email is new if allow_verify and email_data.get('verified') is True: emailaddress.verified = True emailaddress.save() if emailaddress.verified is False and created is True and send_confirmations is True: # new email address send a confirmation emailaddress.send_confirmation() if not emailaddress.verified: continue # only verified email addresses may have variants. Don't bother trying otherwise. requested_variants = email_data.get('cached_variant_emails', []) existing_variant_emails = emailaddress.cached_variant_emails() for requested_variant in requested_variants: if requested_variant not in existing_variant_emails: EmailAddressVariant.objects.create( canonical_email=emailaddress, email=requested_variant ) # remove old items for emailaddress in self.email_items: if emailaddress.email.lower() not in (lower_case_idx.lower() for lower_case_idx in new_email_idx): emailaddress.delete() if self.email != requested_primary: self.email = requested_primary['email'] self.save() def cached_email_variants(self): return chain.from_iterable(email.cached_variants() for email in self.cached_emails()) def can_add_variant(self, email): try: canonical_email = CachedEmailAddress.objects.get(email=email, user=self, verified=True) except CachedEmailAddress.DoesNotExist: return False if email != canonical_email.email \ and email not in [e.email for e in canonical_email.cached_variants()] \ and EmailAddressVariant(email=email, canonical_email=canonical_email).is_valid(): return True return False @property def primary_email(self): primaries = [e for e in self.cached_emails() if e.primary] if len(primaries) > 0: return primaries[0].email return self.email @property def verified_emails(self): return [e for e in self.cached_emails() if e.verified] @property def verified(self): if self.is_superuser: return True if len(self.all_verified_recipient_identifiers) > 0: return True return False @property def all_recipient_identifiers(self): return [e.email for e in self.cached_emails()] + \ [e.email for e in self.cached_email_variants()] + \ self.cached_verified_urls() + \ self.cached_verified_phone_numbers() @property def all_verified_recipient_identifiers(self): return ([e.email for e in self.cached_emails() if e.verified] + [e.email for e in self.cached_email_variants()] + self.cached_verified_urls() + self.cached_verified_phone_numbers()) def is_email_verified(self, email): if email in self.all_verified_recipient_identifiers: return True try: app_infos = ApplicationInfo.objects.filter(application__user=self) if any(app_info.trust_email_verification for app_info in app_infos): return True except ApplicationInfo.DoesNotExist: return False return False @cachemodel.cached_method(auto_publish=True) def cached_issuers(self): return Issuer.objects.filter(staff__id=self.id).distinct() @property def peers(self): """ a BadgeUser is a Peer of another BadgeUser if they appear in an IssuerStaff together """ return set(chain(*[[s.cached_user for s in i.cached_issuerstaff()] for i in self.cached_issuers()])) def cached_badgeclasses(self): return chain.from_iterable(issuer.cached_badgeclasses() for issuer in self.cached_issuers()) @cachemodel.cached_method(auto_publish=True) def cached_badgeinstances(self): return BadgeInstance.objects.filter(recipient_identifier__in=self.all_recipient_identifiers) @cachemodel.cached_method(auto_publish=True) def cached_externaltools(self): return [a.cached_externaltool for a in self.externaltooluseractivation_set.filter(is_active=True)] @cachemodel.cached_method(auto_publish=True) def cached_token(self): user_token, created = \ Token.objects.get_or_create(user=self) return user_token.key @cachemodel.cached_method(auto_publish=True) def cached_agreed_terms_version(self): try: return self.termsagreement_set.all().order_by('-terms_version')[0] except IndexError: pass return None @property def agreed_terms_version(self): v = self.cached_agreed_terms_version() if v is None: return 0 return v.terms_version @agreed_terms_version.setter def agreed_terms_version(self, value): try: value = int(value) except ValueError as e: return if value > self.agreed_terms_version: if TermsVersion.active_objects.filter(version=value).exists(): if not self.pk: self.save() self.termsagreement_set.get_or_create(terms_version=value, defaults=dict(agreed=True)) def replace_token(self): Token.objects.filter(user=self).delete() # user_token, created = \ # Token.objects.get_or_create(user=self) self.save() return self.cached_token() def save(self, *args, **kwargs): if not self.username: self.username = generate_badgr_username(self.email) if getattr(settings, 'BADGEUSER_SKIP_LAST_LOGIN_TIME', True): # skip saving last_login to the database if 'update_fields' in kwargs and kwargs['update_fields'] is not None and 'last_login' in kwargs['update_fields']: kwargs['update_fields'].remove('last_login') if len(kwargs['update_fields']) < 1: # nothing to do, abort so we dont call .publish() return return super(BadgeUser, self).save(*args, **kwargs)
class BadgeUser(UserCachedObjectGetterMixin, UserPermissionsMixin, AbstractUser, BaseVersionedEntity): """ A full-featured user model that can be an Earner, Issuer, or Consumer of Open Badges """ entity_class_name = 'BadgeUser' is_teacher = models.BooleanField(default=False) invited = models.BooleanField(default=False) # The validated name from eduID validated_name = models.CharField(_('validated name'), max_length=254, blank=True, null=True) # Override from AbstractUser first_name = models.CharField(_('first name'), max_length=254, blank=True) last_name = models.CharField(_('last name'), max_length=254, blank=True) badgrapp = models.ForeignKey('mainsite.BadgrApp', on_delete=models.SET_NULL, blank=True, null=True, default=None) is_staff = models.BooleanField( _('Backend-staff member'), default=False, help_text=_( 'Designates whether the user can log into this admin site.'), ) # canvas LTI id lti_id = models.CharField(unique=True, max_length=50, default=None, null=True, blank=True, help_text='LTI user id, unique per user') marketing_opt_in = models.BooleanField(default=False) objects = BadgeUserManager() institution = models.ForeignKey('institution.Institution', on_delete=models.CASCADE, null=True) class Meta: verbose_name = _('badge user') verbose_name_plural = _('badge users') db_table = 'users' permissions = ( ('view_issuer_tab', 'User can view Issuer tab in front end'), ('view_management_tab', 'User can view Management dashboard'), ('has_faculty_scope', 'User has faculty scope'), ('has_institution_scope', 'User has institution scope'), ('ui_issuer_add', 'User can add issuer in front end'), ) def __unicode__(self): return "{} <{}>".format(self.get_full_name(), self.email) @property def email_items(self): return self.cached_emails() @email_items.setter def email_items(self, value): """ Update this users EmailAddress from a list of BadgeUserEmailSerializerV2 data :param value: list(BadgeUserEmailSerializerV2) :return: None """ if len(value) < 1: raise ValidationError("Must have at least 1 email") new_email_idx = {d['email']: d for d in value} primary_count = sum(1 if d.get('primary', False) else 0 for d in value) if primary_count != 1: raise ValidationError("Must have exactly 1 primary email") with transaction.atomic(): # add or update existing items for email_data in value: primary = email_data.get('primary', False) emailaddress, created = CachedEmailAddress.cached.get_or_create( email=email_data['email'], defaults={ 'user': self, 'primary': primary }) if created: # new email address send a confirmation emailaddress.send_confirmation() else: if emailaddress.user_id == self.id: # existing email address owned by user emailaddress.primary = primary emailaddress.save() elif not emailaddress.verified: # existing unverified email address, handover to this user emailaddress.user = self emailaddress.primary = primary emailaddress.save() emailaddress.send_confirmation() else: # existing email address used by someone else raise ValidationError( "Email '{}' may already be in use".format( email_data.get('email'))) # remove old items for emailaddress in self.email_items: if emailaddress.email not in new_email_idx: emailaddress.delete() @property def current_symmetric_key(self): return self.symmetrickey_set.get(current=True) def accept_terms(self, terms): agreement, _ = TermsAgreement.objects.get_or_create(user=self, terms=terms) agreement.agreed_version = terms.version agreement.agreed = True agreement.save() def accept_general_terms(self): general_terms = Terms.get_general_terms(self) for terms in general_terms: self.accept_terms(terms) self.remove_cached_data(['cached_terms_agreements']) def general_terms_accepted(self): nr_accepted = 0 general_terms = Terms.get_general_terms(self) for general_term in general_terms: if general_term.has_been_accepted_by(self): nr_accepted += 1 return general_terms.__len__() == nr_accepted @property def full_name(self): return self.get_full_name() def get_full_name(self): if self.first_name and self.last_name: return "%s %s" % (self.first_name, self.last_name) else: return '' def clear_affiliations(self): """Removes all affiliations""" affiliations = StudentAffiliation.objects.filter(user=self) for affiliation in affiliations: affiliation.delete() self.remove_cached_data(['cached_affiliations']) def add_affiliations(self, affiliations): """ param: affiliations: list of dicts [{'eppn': <eppn>m 'schac_home': <schac_home>}] """ for affiliation in affiliations: StudentAffiliation.objects.get_or_create(user=self, **affiliation) self.remove_cached_data(['cached_affiliations']) @property def eppns(self): return [aff.eppn for aff in self.cached_affiliations()] @property def schac_homes(self): if self.is_teacher: return [self.institution.identifier] return [aff.schac_home for aff in self.cached_affiliations()] @property def direct_awards(self): return DirectAward.objects.filter(eppn__in=self.eppns, status='Unaccepted') def match_provisionments(self): """Used to match provisions on initial login""" provisions = UserProvisionment.objects.filter( email=self.email, for_teacher=self.is_teacher) for provision in provisions: provision.match_user(self) return provisions def email_user(self, subject, html_message): """ Sends an email to this User. """ if settings.LOCAL_DEVELOPMENT_MODE: open_mail_in_browser(html_message) try: EmailBlacklist.objects.get(email=self.primary_email) except EmailBlacklist.DoesNotExist: # Allow sending, as this email is not blacklisted. plain_text = strip_tags(html_message) send_mail(subject, message=plain_text, html_message=html_message, recipient_list=[self.primary_email]) else: return # TODO: Report email non-delivery somewhere. def publish(self): super(BadgeUser, self).publish() self.publish_by('username') def delete(self, *args, **kwargs): super(BadgeUser, self).delete(*args, **kwargs) cache.clear() def can_add_variant(self, email): try: canonical_email = CachedEmailAddress.objects.get(email=email, user=self, verified=True) except CachedEmailAddress.DoesNotExist: return False if email != canonical_email.email \ and email not in [e.email for e in canonical_email.cached_variants()] \ and EmailAddressVariant(email=email, canonical_email=canonical_email).is_valid(): return True return False @property def primary_email(self): primaries = [e for e in self.cached_emails() if e.primary] if len(primaries) > 0: return primaries[0].email return self.email @property def verified_emails(self): return [e for e in self.cached_emails() if e.verified] @property def verified(self): if self.is_superuser: return True if len(self.verified_emails) > 0: return True return False @property def all_recipient_identifiers(self): return [self.get_recipient_identifier()] def get_recipient_identifier(self): from allauth.socialaccount.models import SocialAccount try: account = SocialAccount.objects.get(user=self.pk) return account.uid except SocialAccount.DoesNotExist: return None def get_social_account(self): from allauth.socialaccount.models import SocialAccount try: account = SocialAccount.objects.get(user=self.pk) return account except SocialAccount.DoesNotExist: return None @property def is_student(self): return not self.is_teacher def get_assertions_ready_for_signing(self): assertion_timestamps = AssertionTimeStamp.objects.filter( signer=self).exclude(proof='') return [ ts.badge_instance for ts in assertion_timestamps if ts.badge_instance.signature == None ] def replace_token(self): Token.objects.filter(user=self).delete() # user_token, created = \ # Token.objects.get_or_create(user=self) self.save() return self.cached_token() def save(self, *args, **kwargs): if not self.username: self.username = generate_badgr_username(self.email) if getattr(settings, 'BADGEUSER_SKIP_LAST_LOGIN_TIME', True): # skip saving last_login to the database if 'update_fields' in kwargs and kwargs[ 'update_fields'] is not None and 'last_login' in kwargs[ 'update_fields']: kwargs['update_fields'].remove('last_login') if len(kwargs['update_fields']) < 1: # nothing to do, abort so we dont call .publish() return return super(BadgeUser, self).save(*args, **kwargs)