from annoying.fields import AutoOneToOneField
from django.conf import settings
from django.core.exceptions import ValidationError, PermissionDenied
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.timezone import now
from mentoring.models import Relationship
[docs]class Preferences(models.Model):
"""
Records the mentorship opinions of a User.
"""
class Meta(object):
verbose_name_plural = 'Preferences'
user = AutoOneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name='mentorship_preferences',
primary_key=True)
is_seeking_mentor = models.BooleanField(default=False)
is_seeking_mentee = models.BooleanField(default=False)
mentor_requirements = models.TextField(blank=True)
mentee_requirements = models.TextField(blank=True)
[docs]class InvitationManager(models.Manager):
"""
Model manager for :py:class:`.Invitation` model.
"""
[docs] def active(self):
"""Return a query set giving only the active invitations."""
return self.filter(deactivated_on__isnull=True)
[docs]class Invitation(models.Model):
"""
An invitation to form a mentoring relationship.
Invites are the mechanism where mentor/mentee relationships are created. The
:py:meth:`.clean` method of this model is overridden to allow invites to be
created by anyone but, in that case, they need to be the mentor or mentee of
the invite. Users with the "add_invitation" permission can invite any two
users to form a mentor/mentee relationship.
Each invitation records who the mentor and mentee are to be and the user who
created the invite. (This is useful to determine which of the mentors and
mentees should actually be notified.) The creation date is also stored to
allow for some form of automatic expiry.
An "active" invite is one which is not expired and is still awaiting a
response from the mentor or mentee. An invitation which is declined by
either mentor or mentee should be marked as inactive even if the other party
has not responded.
The :py:attr:`.deactivated_on` date records when this invite became inactive
either through being declined by one party, accepted by both or manually
deactivated.
Should the invite result in a new relationship, this is recorded in the
:py:attr:`.created_relationship` field.
"""
ACCEPT = 'A'
DECLINE = 'D'
objects = InvitationManager()
#: The possible responses to an invitation.
RESPONSES = ((ACCEPT, 'Accept'), (DECLINE, 'Decline'))
#: The proposed mentor for this relationship
mentor = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name='mentor_invitations')
#: The proposed mentee for this relationship
mentee = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name='mentee_invitations')
#: The User who created this invitation
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='created_invitations')
#: The date this invitation was created
created_on = models.DateField(auto_now_add=True)
#: The response from the mentor to the invite
mentor_response = models.CharField(max_length=1, choices=RESPONSES,
blank=True)
#: The response from the mentee to the invite
mentee_response = models.CharField(max_length=1, choices=RESPONSES,
blank=True)
#: If inactive, when did this invite become so
deactivated_on = models.DateField(blank=True, null=True)
#: If this invite lead to a mentoring relationship, it is recorded here
created_relationship = models.ForeignKey(
Relationship, blank=True, null=True,
related_name='started_by_invitations')
[docs] def respond(self, user, accepted):
"""
Set the response of the specified user. If the user is neither the
mentor or mentee then a PermissionDenied exception is raised.
"""
response = Invitation.ACCEPT if accepted else Invitation.DECLINE
if user.id == self.mentor.id:
self.mentor_response = response
elif user.id == self.mentee.id:
self.mentee_response = response
else:
raise PermissionDenied('User is neither mentor or mentee')
# Either mentor or mentee declining deactivates the invite
if not accepted:
self.deactivate()
[docs] def deactivate(self):
"""
Deactivate this invite without creating a relationship.
Does nothing if the invite is already deactivated.
"""
if self.deactivated_on is None:
self.deactivated_on = now()
[docs] def clean(self):
"""
Extra validation for invitations.
Creating invitations when the creator is not the mentor or mentee
requires that the creator have the "add_invitation" permission.
"""
# Check that either a) the creator of the invitation is one of the
# mentor or mentee or b) that the creator has the required permission.
creator_is_matchmaker = self.created_by.has_perm('matching.add_invitation')
creator_is_mentor = self.mentor.id == self.created_by.id
creator_is_mentee = self.mentee.id == self.created_by.id
creator_is_mentor_or_mentee = creator_is_mentor or creator_is_mentee
if not creator_is_matchmaker and not creator_is_mentor_or_mentee:
raise ValidationError('Creator must be one of the mentor or mentee')
# If the creator is one of mentor or mentee, they are assumed to have
# accepted the invite
if creator_is_mentor:
self.mentor_response = Invitation.ACCEPT
if creator_is_mentee:
self.mentee_response = Invitation.ACCEPT
# defer to base class
return super(Invitation, self).clean()
[docs] def is_accepted(self):
"""Returns `True` iff both the mentee and mentor have accepted the
invite.
"""
mentor_accepted = self.mentor_response == Invitation.ACCEPT
mentee_accepted = self.mentee_response == Invitation.ACCEPT
return mentor_accepted and mentee_accepted
[docs] def is_active(self):
"""Returns `True` iff the invite is active (i.e. the "deactivated_on"
date is blank).
"""
return self.deactivated_on is None
@receiver(pre_save, sender=Invitation, dispatch_uid='invitation_relationships')
[docs]def invitation_create_relationships(instance, **_):
"""
A pre-save hook for Invitation instances which creates a mentoring
relationship if:
* The invitation is accepted
* The invitation is active
* There is no current relationship
"""
# Invites must be active and accepted
if not instance.is_active() or not instance.is_accepted():
return
# Invites must not have an existing relationship
if instance.created_relationship is not None:
return
# OK, create the relationship and de-activate the invite
instance.created_relationship = Relationship.objects.create(
mentor=instance.mentor, mentee=instance.mentee)
instance.deactivated_on = now()