Skip to content

Views

hypha.apply.api.v1.review.views

SubmissionReviewViewSet

Bases: BaseStreamForm, WagtailSerializer, SubmissionNestedMixin, GenericViewSet

submission_form_class class-attribute instance-attribute

submission_form_class = PageStreamBaseForm

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

permission_classes class-attribute instance-attribute

permission_classes = (IsAuthenticated, IsApplyStaffUser)

permission_classes_by_action class-attribute instance-attribute

permission_classes_by_action = {'create': [IsAuthenticated, HasReviewCreatePermission, IsApplyStaffUser], 'retrieve': [IsAuthenticated, HasReviewDetailPermission, IsApplyStaffUser], 'update': [IsAuthenticated, HasReviewEditPermission, IsApplyStaffUser], 'delete': [IsAuthenticated, HasReviewDeletePermission, IsApplyStaffUser], 'opinions': [IsAuthenticated, HasReviewOpinionPermission, IsApplyStaffUser], 'fields': [IsAuthenticated, HasReviewCreatePermission, IsApplyStaffUser], 'draft': [IsAuthenticated, HasReviewDraftPermission, IsApplyStaffUser]}

serializer_class class-attribute instance-attribute

serializer_class = SubmissionReviewSerializer

get_submission_object

get_submission_object()
Source code in hypha/apply/api/v1/mixin.py
def get_submission_object(self):
    return get_object_or_404(ApplicationSubmission, id=self.kwargs["submission_pk"])

get_serializer_fields

get_serializer_fields(draft=False)

Get the respective serializer fields for all the form fields.

Source code in hypha/apply/api/v1/stream_serializers.py
def get_serializer_fields(self, draft=False):
    """
    Get the respective serializer fields for all the form fields.
    """
    serializer_fields = OrderedDict()
    form_fields = self.get_form_fields()
    for field_id, field in form_fields.items():
        serializer_fields[field_id] = self._get_field(
            field, self.get_serializer_field_class(field), draft
        )
    return serializer_fields

find_function_args

find_function_args(func)

Get the list of parameter names which function accepts.

Source code in hypha/apply/api/v1/stream_serializers.py
def find_function_args(self, func):
    """
    Get the list of parameter names which function accepts.
    """
    try:
        spec = (
            inspect.getfullargspec(func)
            if hasattr(inspect, "getfullargspec")
            else inspect.getargspec(func)
        )
        return [i for i in spec[0] if i not in IGNORE_ARGS]
    except TypeError:
        return []

find_class_args

find_class_args(klass)

Find all class arguments (parameters) which can be passed in __init__.

Source code in hypha/apply/api/v1/stream_serializers.py
def find_class_args(self, klass):
    """
    Find all class arguments (parameters) which can be passed in ``__init__``.
    """
    args = set()
    for i in klass.mro():
        if i is object or not hasattr(i, "__init__"):
            continue
        args |= set(self.find_function_args(i.__init__))

    return list(args)

find_matching_class_kwargs

find_matching_class_kwargs(reference_object, klass)
Source code in hypha/apply/api/v1/stream_serializers.py
def find_matching_class_kwargs(self, reference_object, klass):
    return {
        i: getattr(reference_object, i)
        for i in self.find_class_args(klass)
        if hasattr(reference_object, i)
    }

get_serializer_field_class

get_serializer_field_class(field)

Assumes that a serializer field exist with the same name as form field.

TODO: In case there are form fields not existing in serializer fields, we would have to create mapping b/w form fields and serializer fields to get the respective classes. But for now this works.

Source code in hypha/apply/api/v1/stream_serializers.py
def get_serializer_field_class(self, field):
    """
    Assumes that a serializer field exist with the same name as form field.

    TODO:
    In case there are form fields not existing in serializer fields, we would
    have to create mapping b/w form fields and serializer fields to get the
    respective classes. But for now this works.
    """
    if isinstance(field, BlockFieldWrapper):
        return serializers.CharField
    if isinstance(field, ScoredAnswerField):
        return ScoredAnswerListField
    if isinstance(field, TypedChoiceField):
        return serializers.ChoiceField
    class_name = field.__class__.__name__
    return getattr(serializers, class_name)

from_db classmethod

from_db(db, field_names, values)
Source code in hypha/apply/stream_forms/models.py
@classmethod
def from_db(cls, db, field_names, values):
    instance = super().from_db(db, field_names, values)
    if "form_data" in field_names:
        instance.form_data = cls.deserialize_form_data(
            instance, instance.form_data, instance.form_fields
        )
    return instance

deserialize_form_data classmethod

deserialize_form_data(instance, form_data, form_fields)
Source code in hypha/apply/stream_forms/models.py
@classmethod
def deserialize_form_data(cls, instance, form_data, form_fields):
    data = form_data.copy()
    # PERFORMANCE NOTE:
    # Do not attempt to iterate over form_fields - that will fully instantiate the form_fields
    # including any sub queries that they do
    for _i, field_data in enumerate(form_fields.raw_data):
        block = form_fields.stream_block.child_blocks[field_data["type"]]
        field_id = field_data.get("id")
        try:
            value = data[field_id]
        except KeyError:
            pass
        else:
            data[field_id] = block.decode(value)
    return data

get_form_fields

get_form_fields(draft=False, form_data=None, user=None)
Source code in hypha/apply/stream_forms/models.py
def get_form_fields(self, draft=False, form_data=None, user=None):
    if form_data is None:
        form_data = {}

    form_fields = OrderedDict()
    field_blocks = self.get_defined_fields()
    group_counter = 1
    is_in_group = False

    # If true option 1 is selected
    grouped_fields_visible = False
    for struct_child in field_blocks:
        block = struct_child.block
        struct_value = struct_child.value
        if isinstance(block, FormFieldBlock):
            field_from_block = block.get_field(struct_value)
            disabled_help_text = _(
                "You are logged in so this information is fetched from your user account."
            )
            if isinstance(block, FullNameBlock) and user and user.is_authenticated:
                if user.full_name:
                    field_from_block.disabled = True
                    field_from_block.initial = user.full_name
                    field_from_block.help_text = disabled_help_text
                else:
                    field_from_block.help_text = _(
                        "You are logged in but your user account does not have a "
                        "full name. We'll update your user account with the name you provide here."
                    )
            if isinstance(block, EmailBlock) and user and user.is_authenticated:
                field_from_block.disabled = True
                field_from_block.initial = user.email
                field_from_block.help_text = disabled_help_text
            if draft and not issubclass(
                block.__class__, ApplicationMustIncludeFieldBlock
            ):
                field_from_block.required = False
            field_from_block.help_link = struct_value.get("help_link")
            field_from_block.group_number = group_counter if is_in_group else 1
            if isinstance(block, GroupToggleBlock) and not is_in_group:
                field_from_block.group_number = 1
                field_from_block.grouper_for = group_counter + 1
                group_counter += 1
                is_in_group = True
                grouped_fields_visible = (
                    form_data.get(struct_child.id) == field_from_block.choices[0][0]
                )
            if isinstance(block, TextFieldBlock):
                field_from_block.word_limit = struct_value.get("word_limit")
            if isinstance(block, MultiInputCharFieldBlock):
                number_of_inputs = struct_value.get("number_of_inputs")
                for index in range(number_of_inputs):
                    form_fields[struct_child.id + "_" + str(index)] = (
                        field_from_block
                    )
                    field_from_block.multi_input_id = struct_child.id
                    field_from_block.add_button_text = struct_value.get(
                        "add_button_text"
                    )
                    if (
                        index == number_of_inputs - 1
                    ):  # Add button after last input field
                        field_from_block.multi_input_add_button = True
                        # Index for field until which fields will be visible to applicant.
                        # Initially only the first field with id UUID_0 will be visible.
                        field_from_block.visibility_index = 0
                        field_from_block.max_index = index
                    if index != 0:
                        field_from_block.multi_input_field = True
                        field_from_block.required = False
                        field_from_block.initial = None
                    field_from_block = copy.copy(field_from_block)
            else:
                if is_in_group and not isinstance(block, GroupToggleBlock):
                    field_from_block.required_when_visible = (
                        field_from_block.required
                    )
                    field_from_block.required = (
                        field_from_block.required & grouped_fields_visible
                    )
                    field_from_block.visible = grouped_fields_visible
                form_fields[struct_child.id] = field_from_block
        elif isinstance(block, GroupToggleEndBlock):
            # Group toggle end block is used only to group fields and not used in actual form.
            # Todo: Use streamblock to create nested form field blocks, a more elegant method to group form fields.
            is_in_group = False
        else:
            field_wrapper = BlockFieldWrapper(struct_child)
            field_wrapper.group_number = group_counter if is_in_group else 1
            form_fields[struct_child.id] = field_wrapper

    return form_fields

get_form_class

get_form_class(draft=False, form_data=None, user=None)
Source code in hypha/apply/stream_forms/models.py
def get_form_class(self, draft=False, form_data=None, user=None):
    return type(
        "WagtailStreamForm",
        (self.submission_form_class,),
        self.get_form_fields(draft, form_data, user),
    )

get_permissions

get_permissions()
Source code in hypha/apply/api/v1/review/views.py
def get_permissions(self):
    try:
        # return permission_classes depending on `action`
        return [
            permission()
            for permission in self.permission_classes_by_action[self.action]
        ]
    except KeyError:
        # action is not set return default permission_classes
        return [permission() for permission in self.permission_classes]

get_defined_fields

get_defined_fields()

Get form fields created for reviewing this submission.

These form fields will be used to get respective serializer fields.

Source code in hypha/apply/api/v1/review/views.py
def get_defined_fields(self):
    """
    Get form fields created for reviewing this submission.

    These form fields will be used to get respective serializer fields.
    """
    if self.action in ["retrieve", "update", "opinions"]:
        # For detail and edit api form fields used while submitting
        # review should be used.
        review = self.get_object()
        return review.form_fields
    if self.action == "draft":
        review = self.get_review_by_reviewer()
        return review.form_fields
    submission = self.get_submission_object()
    return get_review_form_fields_for_stage(submission)

get_serializer_class

get_serializer_class()

Override get_serializer_class to send draft parameter if the request is to save as draft or the review submitted is saved as draft.

Source code in hypha/apply/api/v1/review/views.py
def get_serializer_class(self):
    """
    Override get_serializer_class to send draft parameter
    if the request is to save as draft or the review submitted
    is saved as draft.
    """
    if self.action == "retrieve":
        review = self.get_object()
        draft = review.is_draft
    elif self.action == "draft":
        draft = True
    else:
        draft = self.request.data.get("is_draft", False)
    return super().get_serializer_class(draft)

get_queryset

get_queryset()
Source code in hypha/apply/api/v1/review/views.py
def get_queryset(self):
    submission = self.get_submission_object()
    return Review.objects.filter(submission=submission, is_draft=False)

get_object

get_object()

Get the review object by id. If not found raise 404.

Source code in hypha/apply/api/v1/review/views.py
def get_object(self):
    """
    Get the review object by id. If not found raise 404.
    """
    queryset = self.get_queryset()
    obj = get_object_or_404(queryset, id=self.kwargs["pk"])
    self.check_object_permissions(self.request, obj)
    return obj

get_reviewer

get_reviewer()

Get the AssignedReviewers for the current user on a submission.

Source code in hypha/apply/api/v1/review/views.py
def get_reviewer(self):
    """
    Get the AssignedReviewers for the current user on a submission.
    """
    submission = self.get_submission_object()
    ar, _ = AssignedReviewers.objects.get_or_create_for_user(
        submission=submission,
        reviewer=self.request.user,
    )
    return ar

create

create(request, *args, **kwargs)

Create a review on a submission.

Accept a post data in form of {field_id: value}. field_id is same id which you get from the /fields api. value should be submitted with html tags, so that response can be displayed with correct formatting, e.g. in case of rich text field, we need to show the data with same formatting user has submitted.

Accepts optional parameter is_draft when a review is to be saved as draft.

Raise ValidationError if a review is already submitted by the user.

Source code in hypha/apply/api/v1/review/views.py
def create(self, request, *args, **kwargs):
    """
    Create a review on a submission.

    Accept a post data in form of `{field_id: value}`.
    `field_id` is same id which you get from the `/fields` api.
    `value` should be submitted with html tags, so that response can
    be displayed with correct formatting, e.g. in case of rich text field,
    we need to show the data with same formatting user has submitted.

    Accepts optional parameter `is_draft` when a review is to be saved as draft.

    Raise ValidationError if a review is already submitted by the user.
    """
    submission = self.get_submission_object()
    ser = self.get_serializer(data=request.data)
    ser.is_valid(raise_exception=True)
    instance, create = ser.Meta.model.objects.get_or_create(
        submission=submission, author=self.get_reviewer()
    )
    if not create and not instance.is_draft:
        raise ValidationError(
            {"detail": "You have already posted a review for this submission"}
        )
    instance.form_fields = self.get_defined_fields()
    instance.save()
    ser.update(instance, ser.validated_data)
    if not instance.is_draft:
        messenger(
            MESSAGES.NEW_REVIEW,
            request=self.request,
            user=self.request.user,
            source=submission,
            related=instance,
        )
        # Automatic workflow actions.
        review_workflow_actions(self.request, submission)
    ser = self.get_serializer(self.get_review_data(instance))
    return Response(ser.data, status=status.HTTP_201_CREATED)

get_review_data

get_review_data(review)

Get review data which will be used for review detail api.

Source code in hypha/apply/api/v1/review/views.py
def get_review_data(self, review):
    """
    Get review data which will be used for review detail api.
    """
    review_data = review.form_data
    review_data["id"] = review.id
    review_data["score"] = review.score
    review_data["opinions"] = review.opinions
    review_data["is_draft"] = review.is_draft
    for field_block in review.form_fields:
        if isinstance(field_block.block, RichTextBlock):
            review_data[field_block.id] = field_block.value.source
    return review_data

retrieve

retrieve(request, *args, **kwargs)

Get details of a review on a submission

Source code in hypha/apply/api/v1/review/views.py
def retrieve(self, request, *args, **kwargs):
    """
    Get details of a review on a submission
    """
    review = self.get_object()
    ser = self.get_serializer(self.get_review_data(review))
    return Response(ser.data)

update

update(request, *args, **kwargs)

Update a review submitted on a submission.

Source code in hypha/apply/api/v1/review/views.py
def update(self, request, *args, **kwargs):
    """
    Update a review submitted on a submission.
    """
    review = self.get_object()
    ser = self.get_serializer(data=request.data)
    ser.is_valid(raise_exception=True)
    ser.update(review, ser.validated_data)

    messenger(
        MESSAGES.EDIT_REVIEW,
        user=self.request.user,
        request=self.request,
        source=review.submission,
        related=review,
    )
    # Automatic workflow actions.
    review_workflow_actions(self.request, review.submission)
    ser = self.get_serializer(self.get_review_data(review))
    return Response(ser.data)

destroy

destroy(request, *args, **kwargs)

Delete a review on a submission

Source code in hypha/apply/api/v1/review/views.py
def destroy(self, request, *args, **kwargs):
    """Delete a review on a submission"""
    review = self.get_object()
    messenger(
        MESSAGES.DELETE_REVIEW,
        user=request.user,
        request=request,
        source=review.submission,
        related=review,
    )
    review.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

get_review_by_reviewer

get_review_by_reviewer()
Source code in hypha/apply/api/v1/review/views.py
def get_review_by_reviewer(self):
    submission = self.get_submission_object()
    review = Review.objects.get(
        submission=submission, author__reviewer=self.request.user
    )
    return review

draft

draft(request, *args, **kwargs)

Returns the draft review submitted on a submission by current user.

Source code in hypha/apply/api/v1/review/views.py
@action(detail=False, methods=["get"])
def draft(self, request, *args, **kwargs):
    """
    Returns the draft review submitted on a submission by current user.
    """
    try:
        review = self.get_review_by_reviewer()
    except Review.DoesNotExist:
        return Response({})
    if not review.is_draft:
        return Response({})
    ser = self.get_serializer(self.get_review_data(review))
    return Response(ser.data)

fields

fields(request, *args, **kwargs)

List details of all the form fields that were created by admin for adding reviews.

These field details will be used in frontend to render the review form.

Source code in hypha/apply/api/v1/review/views.py
@action(detail=False, methods=["get"])
def fields(self, request, *args, **kwargs):
    """
    List details of all the form fields that were created by admin for adding reviews.

    These field details will be used in frontend to render the review form.
    """
    fields = self.get_form_fields()
    fields = FieldSerializer(fields.items(), many=True)
    return Response(fields.data)

opinions

opinions(request, *args, **kwargs)

Used to add opinions on a review.

Options are 0 and 1. DISAGREE = 0 AGREE = 1

Response is similar to detail api of the review.

Source code in hypha/apply/api/v1/review/views.py
@action(detail=True, methods=["post"])
def opinions(self, request, *args, **kwargs):
    """
    Used to add opinions on a review.

    Options are 0 and 1. DISAGREE = 0 AGREE = 1

    Response is similar to detail api of the review.
    """
    review = self.get_object()
    ser = ReviewOpinionWriteSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    opinion = ser.validated_data["opinion"]
    try:
        review_opinion = ReviewOpinion.objects.get(
            review=review, author=self.get_reviewer()
        )
    except ReviewOpinion.DoesNotExist:
        ReviewOpinion.objects.create(
            review=review, author=self.get_reviewer(), opinion=opinion
        )
    else:
        review_opinion.opinion = opinion
        review_opinion.save()
    ser = self.get_serializer(self.get_review_data(review))
    return Response(ser.data, status=status.HTTP_201_CREATED)