Skip to content

Models

hypha.apply.activity.models

COMMENT module-attribute

COMMENT = 'comment'

ACTION module-attribute

ACTION = 'action'

ACTIVITY_TYPES module-attribute

ACTIVITY_TYPES = {COMMENT: gettext('Comment'), ACTION: gettext('Action')}

APPLICANT module-attribute

APPLICANT = gettext('applicant')

TEAM module-attribute

TEAM = gettext('team')

REVIEWER module-attribute

REVIEWER = gettext('reviewers')

PARTNER module-attribute

PARTNER = gettext('partners')

ALL module-attribute

ALL = gettext('all')

APPLICANT_PARTNERS module-attribute

APPLICANT_PARTNERS = f'{APPLICANT} {PARTNER}'

VISIBILITY module-attribute

VISIBILITY = {APPLICANT: gettext('Applicants'), TEAM: gettext('Staff only'), REVIEWER: gettext('Reviewers'), PARTNER: gettext('Partners'), ALL: gettext('All'), APPLICANT_PARTNERS: gettext('Applicants & Partners')}

BaseActivityQuerySet

Bases: QuerySet

visible_to

visible_to(user)

Get a QuerySet of all items that are visible to the given user.

Parameters:

  • user –

    User to filter visibility of

Returns:

  • QuerySet –

    A QuerySet containing all items visible to the specified user

Source code in hypha/apply/activity/models.py
def visible_to(self, user) -> models.QuerySet:
    """Get a QuerySet of all items that are visible to the given user.

    Args:
        user:
            [`User`][hypha.apply.users.models.User] to filter visibility of

    Returns:
        A QuerySet containing all items visible to the specified user
    """

    # To hide reviews from the applicant's activity feed
    # Todo: It is just for historic data and not be needed for new data after this.
    from .messaging import ActivityAdapter

    messages = ActivityAdapter.messages

    user_qs = Q(user=user)

    # There are scenarios where users will have activities in which they
    # wouldn't have visibility just using Activity.visibility_for. Thus,
    # the queryset should include activity in which they author via
    # `user_qs` (ie. A comment made only to staff from a partner).
    if user.is_applicant:
        # Handle the edge case where a partner or reviewer is also an
        # applicant. Ensures that any applications/projects the user
        # authored will have comment visibility of applicant while others
        # will get the appropriate role.
        if user.is_partner or user.is_reviewer:
            ApplicationSubmission = apps.get_model("funds", "ApplicationSubmission")
            Project = apps.get_model("application_projects", "Project")

            app_content_type = ContentType.objects.get_for_model(
                ApplicationSubmission
            )
            proj_content_type = ContentType.objects.get_for_model(Project)

            authored_apps = ApplicationSubmission.objects.filter(user_qs).values(
                "id"
            )
            authored_projs = Project.objects.filter(user_qs).values("id")

            proj_app_qs = (
                Q(source_content_type=app_content_type)
                & Q(source_object_id__in=authored_apps)
            ) | (
                Q(source_content_type=proj_content_type)
                & Q(source_object_id__in=authored_projs)
            )

            # Activities the user is the author of the source submission
            applicant_activity = self.filter(
                proj_app_qs
                & Q(
                    Q(visibility__in=self.model.visibility_for(user, True))
                    | user_qs
                )
            )
            # All other activities
            other_activity = self.exclude(
                Q(message=messages.get(MESSAGES.NEW_REVIEW)) | proj_app_qs
            ).filter(Q(visibility__in=self.model.visibility_for(user)) | user_qs)
            return applicant_activity | other_activity
        else:
            return self.exclude(message=messages.get(MESSAGES.NEW_REVIEW)).filter(
                Q(visibility__in=self.model.visibility_for(user)) | user_qs
            )

    return self.filter(Q(visibility__in=self.model.visibility_for(user)) | user_qs)

newer

newer(activity)
Source code in hypha/apply/activity/models.py
def newer(self, activity):
    return self.filter(timestamp__gt=activity.timestamp)

ActivityQuerySet

Bases: BaseActivityQuerySet

visible_to

visible_to(user)

Get a QuerySet of all items that are visible to the given user.

Parameters:

  • user –

    User to filter visibility of

Returns:

  • QuerySet –

    A QuerySet containing all items visible to the specified user

Source code in hypha/apply/activity/models.py
def visible_to(self, user) -> models.QuerySet:
    """Get a QuerySet of all items that are visible to the given user.

    Args:
        user:
            [`User`][hypha.apply.users.models.User] to filter visibility of

    Returns:
        A QuerySet containing all items visible to the specified user
    """

    # To hide reviews from the applicant's activity feed
    # Todo: It is just for historic data and not be needed for new data after this.
    from .messaging import ActivityAdapter

    messages = ActivityAdapter.messages

    user_qs = Q(user=user)

    # There are scenarios where users will have activities in which they
    # wouldn't have visibility just using Activity.visibility_for. Thus,
    # the queryset should include activity in which they author via
    # `user_qs` (ie. A comment made only to staff from a partner).
    if user.is_applicant:
        # Handle the edge case where a partner or reviewer is also an
        # applicant. Ensures that any applications/projects the user
        # authored will have comment visibility of applicant while others
        # will get the appropriate role.
        if user.is_partner or user.is_reviewer:
            ApplicationSubmission = apps.get_model("funds", "ApplicationSubmission")
            Project = apps.get_model("application_projects", "Project")

            app_content_type = ContentType.objects.get_for_model(
                ApplicationSubmission
            )
            proj_content_type = ContentType.objects.get_for_model(Project)

            authored_apps = ApplicationSubmission.objects.filter(user_qs).values(
                "id"
            )
            authored_projs = Project.objects.filter(user_qs).values("id")

            proj_app_qs = (
                Q(source_content_type=app_content_type)
                & Q(source_object_id__in=authored_apps)
            ) | (
                Q(source_content_type=proj_content_type)
                & Q(source_object_id__in=authored_projs)
            )

            # Activities the user is the author of the source submission
            applicant_activity = self.filter(
                proj_app_qs
                & Q(
                    Q(visibility__in=self.model.visibility_for(user, True))
                    | user_qs
                )
            )
            # All other activities
            other_activity = self.exclude(
                Q(message=messages.get(MESSAGES.NEW_REVIEW)) | proj_app_qs
            ).filter(Q(visibility__in=self.model.visibility_for(user)) | user_qs)
            return applicant_activity | other_activity
        else:
            return self.exclude(message=messages.get(MESSAGES.NEW_REVIEW)).filter(
                Q(visibility__in=self.model.visibility_for(user)) | user_qs
            )

    return self.filter(Q(visibility__in=self.model.visibility_for(user)) | user_qs)

newer

newer(activity)
Source code in hypha/apply/activity/models.py
def newer(self, activity):
    return self.filter(timestamp__gt=activity.timestamp)

comments

comments()
Source code in hypha/apply/activity/models.py
def comments(self):
    return self.filter(type=COMMENT)

actions

actions()
Source code in hypha/apply/activity/models.py
def actions(self):
    return self.filter(type=ACTION)

latest

latest()
Source code in hypha/apply/activity/models.py
def latest(self):
    return self.filter(
        timestamp__gte=(timezone.now() - timezone.timedelta(days=30))
    )

ActivityBaseManager

Bases: Manager

create

create(**kwargs)
Source code in hypha/apply/activity/models.py
def create(self, **kwargs):
    kwargs.update(type=self.type)
    return super().create(**kwargs)

get_queryset

get_queryset()
Source code in hypha/apply/activity/models.py
def get_queryset(self):
    return (
        super()
        .get_queryset()
        .filter(
            type=self.type,
            current=True,
        )
    )

CommentQueryset

CommentManger

Bases: ActivityBaseManager

type class-attribute instance-attribute

type = COMMENT

create

create(**kwargs)
Source code in hypha/apply/activity/models.py
def create(self, **kwargs):
    kwargs.update(type=self.type)
    return super().create(**kwargs)

get_queryset

get_queryset()
Source code in hypha/apply/activity/models.py
def get_queryset(self):
    return (
        super()
        .get_queryset()
        .filter(
            type=self.type,
            current=True,
        )
    )

ActionQueryset

ActionManager

Bases: ActivityBaseManager

type class-attribute instance-attribute

type = ACTION

create

create(**kwargs)
Source code in hypha/apply/activity/models.py
def create(self, **kwargs):
    kwargs.update(type=self.type)
    return super().create(**kwargs)

get_queryset

get_queryset()
Source code in hypha/apply/activity/models.py
def get_queryset(self):
    return (
        super()
        .get_queryset()
        .filter(
            type=self.type,
            current=True,
        )
    )

ActivityAttachment

Bases: Model

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

uuid class-attribute instance-attribute

uuid = UUIDField(unique=True, default=uuid4, editable=False)

activity class-attribute instance-attribute

activity = ForeignKey('Activity', on_delete=CASCADE, related_name='attachments')

file class-attribute instance-attribute

file = FileField(upload_to=get_attachment_upload_path, storage=PrivateStorage())

filename property

filename

get_absolute_url

get_absolute_url()
Source code in hypha/apply/activity/models.py
def get_absolute_url(self):
    return reverse("activity:attachment", kwargs={"file_pk": str(self.uuid)})

Activity

Bases: Model

timestamp class-attribute instance-attribute

timestamp = DateTimeField()

type class-attribute instance-attribute

type = CharField(choices=items(), max_length=30)

user class-attribute instance-attribute

user = ForeignKey(AUTH_USER_MODEL, on_delete=PROTECT)

source_content_type class-attribute instance-attribute

source_content_type = ForeignKey(ContentType, blank=True, null=True, on_delete=CASCADE, related_name='activity_source')

source_object_id class-attribute instance-attribute

source_object_id = PositiveIntegerField(blank=True, null=True, db_index=True)

source class-attribute instance-attribute

source = GenericForeignKey('source_content_type', 'source_object_id')

message class-attribute instance-attribute

message = TextField()

visibility class-attribute instance-attribute

visibility = CharField(choices=list(items()), default=APPLICANT, max_length=30)

edited class-attribute instance-attribute

edited = DateTimeField(default=None, null=True)

current class-attribute instance-attribute

current = BooleanField(default=True)

previous class-attribute instance-attribute

previous = ForeignKey('self', on_delete=CASCADE, null=True)

related_content_type class-attribute instance-attribute

related_content_type = ForeignKey(ContentType, blank=True, null=True, on_delete=CASCADE, related_name='activity_related')

related_object_id class-attribute instance-attribute

related_object_id = PositiveIntegerField(blank=True, null=True)

related_object class-attribute instance-attribute

related_object = GenericForeignKey('related_content_type', 'related_object_id')

objects class-attribute instance-attribute

objects = from_queryset(ActivityQuerySet)()

comments class-attribute instance-attribute

comments = from_queryset(CommentQueryset)()

actions class-attribute instance-attribute

actions = from_queryset(ActionQueryset)()

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

priviledged property

priviledged

private property

private

Meta

ordering class-attribute instance-attribute
ordering = ['-timestamp']
base_manager_name class-attribute instance-attribute
base_manager_name = 'objects'

get_absolute_url

get_absolute_url()
Source code in hypha/apply/activity/models.py
def get_absolute_url(self):
    return f"{self.source.get_absolute_url()}#communications--{self.id}"

visibility_for classmethod

visibility_for(user, is_submission_author=False)

Gets activity visibility for a specified user

Takes an optional boolean that is used to determine the visibility of an application comment. This was mainly implemented to allow partners also holding the role of applicant to have a proper visibility.

ie. Prevent someone with the role of partner & applicant looking at comments on their own application and seeing partner visibility

Parameters:

  • user –

    User to get visibility for

  • is_submission_author (Optional[bool], default: False ) –

    boolean used when the user is the applicant of the source activity

Returns:

  • List[str] –

    A list of visibility strings

Source code in hypha/apply/activity/models.py
@classmethod
def visibility_for(
    cls, user, is_submission_author: Optional[bool] = False
) -> List[str]:
    """Gets activity visibility for a specified user

    Takes an optional boolean that is used to determine the visibility of
    an application comment. This was mainly implemented to allow partners
    also holding the role of applicant to have a proper visibility.

    ie. Prevent someone with the role of partner & applicant looking at
    comments on their own application and seeing partner visibility

    Args:
        user:
            [`User`][hypha.apply.users.models.User] to get visibility for
        is_submission_author:
            boolean used when the `user` is the applicant of the source activity

    Returns:
        A list of visibility strings
    """
    if user.is_apply_staff:
        return [TEAM, APPLICANT, REVIEWER, APPLICANT_PARTNERS, PARTNER, ALL]
    if user.is_reviewer and not is_submission_author:
        return [REVIEWER, ALL]
    if user.is_finance or user.is_contracting:
        # for project part
        return [TEAM, APPLICANT, REVIEWER, PARTNER, ALL]
    if user.is_partner and not is_submission_author:
        return [PARTNER, ALL, APPLICANT_PARTNERS]
    if user.is_applicant:
        return [APPLICANT, ALL, APPLICANT_PARTNERS]

    return [ALL]

visibility_choices_for classmethod

visibility_choices_for(user, submission_partner_list=None)

Gets activity visibility choices for the specified user

Uses the given user (and partner query set if provided) to give the specified user activity visibility choices.

Parameters:

  • user –

    The User being given visibility choices

  • submission_has_partner –

    An optional QuerySet of partners (Users)

Source code in hypha/apply/activity/models.py
@classmethod
def visibility_choices_for(
    cls, user, submission_partner_list: Optional[QuerySet] = None
) -> List[Tuple[str, str]]:
    """Gets activity visibility choices for the specified user

    Uses the given user (and partner query set if provided) to give
    the specified user activity visibility choices.

    Args:
        user:
            The [`User`][hypha.apply.users.models.User] being given
            visibility choices
        submission_has_partner:
            An optional QuerySet of partners
            ([`Users`][hypha.apply.users.models.User])
    Returns:
        A list of tuples in the format of:
        [(<visibility string>, <visibility display string>), ...]
    """
    has_partner = submission_partner_list and len(submission_partner_list) > 0

    if user.is_apply_staff:
        if not has_partner:
            choices = [
                (TEAM, VISIBILITY[TEAM]),
                (APPLICANT, VISIBILITY[APPLICANT]),
                (REVIEWER, VISIBILITY[REVIEWER]),
                (ALL, VISIBILITY[ALL]),
            ]
        else:
            choices = [
                (TEAM, VISIBILITY[TEAM]),
                (APPLICANT, VISIBILITY[APPLICANT]),
                (PARTNER, VISIBILITY[PARTNER]),
                (APPLICANT_PARTNERS, VISIBILITY[APPLICANT_PARTNERS]),
                (REVIEWER, VISIBILITY[REVIEWER]),
                (ALL, VISIBILITY[ALL]),
            ]
        return choices

    if user.is_partner and has_partner and submission_partner_list.contains(user):
        return [
            (APPLICANT_PARTNERS, VISIBILITY[APPLICANT_PARTNERS]),
            (PARTNER, VISIBILITY[PARTNER]),
            (TEAM, VISIBILITY[TEAM]),
        ]

    if user.is_applicant and has_partner:
        return [
            (APPLICANT_PARTNERS, VISIBILITY[PARTNER]),
            (APPLICANT, VISIBILITY[TEAM]),
        ]

    if user.is_applicant:
        return [(APPLICANT, VISIBILITY[APPLICANT])]

    if user.is_reviewer:
        return [(REVIEWER, VISIBILITY[REVIEWER])]

    if user.is_finance or user.is_contracting:
        return [(TEAM, VISIBILITY[TEAM]), (APPLICANT, VISIBILITY[APPLICANT])]

    return [(ALL, VISIBILITY[ALL])]

Event

Bases: Model

Model to track when messages are triggered

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

when class-attribute instance-attribute

when = DateTimeField(auto_now_add=True)

type class-attribute instance-attribute

type = CharField(gettext('verb'), choices=choices, max_length=50)

by class-attribute instance-attribute

by = ForeignKey(AUTH_USER_MODEL, on_delete=PROTECT, null=True)

content_type class-attribute instance-attribute

content_type = ForeignKey(ContentType, blank=True, null=True, on_delete=CASCADE)

object_id class-attribute instance-attribute

object_id = PositiveIntegerField(blank=True, null=True)

source class-attribute instance-attribute

source = GenericForeignKey('content_type', 'object_id')

MessagesQueryset

Bases: QuerySet

update_status

update_status(status)
Source code in hypha/apply/activity/models.py
def update_status(self, status):
    if status:
        return self.update(
            status=Case(
                When(status="", then=Value(status)),
                default=Concat("status", Value("<br />" + status)),
                output_field=models.TextField(),
            ),
        )

Message

Bases: Model

Model to track content of messages sent from an event

type class-attribute instance-attribute

type = CharField(max_length=15)

content class-attribute instance-attribute

content = TextField()

recipient class-attribute instance-attribute

recipient = CharField(max_length=250)

event class-attribute instance-attribute

event = ForeignKey(Event, on_delete=CASCADE)

status class-attribute instance-attribute

status = TextField()

external_id class-attribute instance-attribute

external_id = CharField(max_length=75, null=True, blank=True)

sent_in_email_digest class-attribute instance-attribute

sent_in_email_digest = BooleanField(default=False)

objects class-attribute instance-attribute

objects = as_manager()

update_status

update_status(status)
Source code in hypha/apply/activity/models.py
def update_status(self, status):
    if status:
        self.status = Case(
            When(status="", then=Value(status)),
            default=Concat("status", Value("<br />" + status)),
            output_field=models.TextField(),
        )
        self.save()

get_attachment_upload_path

get_attachment_upload_path(instance, filename)
Source code in hypha/apply/activity/models.py
def get_attachment_upload_path(instance, filename):
    return f"activity/attachments/{instance.id}/{get_valid_filename(filename)}"