Skip to content

Models

hypha.apply.review.models

ReviewFormFieldsMixin

Bases: Model

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

form_fields class-attribute instance-attribute

form_fields = StreamField(ReviewCustomFormFieldsBlock(), use_json_field=True)

score_fields property

score_fields

score_fields_without_text property

score_fields_without_text

recommendation_field property

recommendation_field

visibility_field property

visibility_field

comment_field property

comment_field

Meta

abstract class-attribute instance-attribute
abstract = True

ReviewForm

Bases: ReviewFormFieldsMixin, Model

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

form_fields class-attribute instance-attribute

form_fields = StreamField(ReviewCustomFormFieldsBlock(), use_json_field=True)

score_fields property

score_fields

score_fields_without_text property

score_fields_without_text

recommendation_field property

recommendation_field

visibility_field property

visibility_field

comment_field property

comment_field

name class-attribute instance-attribute

name = CharField(max_length=255)

panels class-attribute instance-attribute

panels = [FieldPanel('name'), FieldPanel('form_fields')]

Meta

abstract class-attribute instance-attribute
abstract = True

ReviewQuerySet

Bases: QuerySet

submitted

submitted()
Source code in hypha/apply/review/models.py
def submitted(self):
    return self.filter(is_draft=False)

by_staff

by_staff()
Source code in hypha/apply/review/models.py
def by_staff(self):
    return self.submitted()._by_group(STAFF_GROUP_NAME)

by_reviewers

by_reviewers()
Source code in hypha/apply/review/models.py
def by_reviewers(self):
    return self.submitted()._by_group(REVIEWER_GROUP_NAME)

by_partners

by_partners()
Source code in hypha/apply/review/models.py
def by_partners(self):
    return self.submitted()._by_group(PARTNER_GROUP_NAME)

by_user

by_user(user)
Source code in hypha/apply/review/models.py
def by_user(self, user):
    return self.submitted().filter(author__reviewer=user).order_by("-created_at")

staff_score

staff_score()
Source code in hypha/apply/review/models.py
def staff_score(self):
    return self.by_staff().score()

staff_recommendation

staff_recommendation()
Source code in hypha/apply/review/models.py
def staff_recommendation(self):
    return self.by_staff().recommendation()

reviewers_score

reviewers_score()
Source code in hypha/apply/review/models.py
def reviewers_score(self):
    return self.by_reviewers().score()

reviewers_recommendation

reviewers_recommendation()
Source code in hypha/apply/review/models.py
def reviewers_recommendation(self):
    return self.by_reviewers().recommendation()

score

score()
Source code in hypha/apply/review/models.py
def score(self):
    return self.exclude(score=NA).aggregate(models.Avg("score"))["score__avg"]

recommendation

recommendation()
Source code in hypha/apply/review/models.py
def recommendation(self):
    opinions = self.values_list("opinions__opinion", flat=True)

    if any(opinion == DISAGREE for opinion in opinions):
        return MAYBE

    recommendations = self.values_list("recommendation", flat=True)
    try:
        recommendation = sum(recommendations) / len(recommendations)
    except ZeroDivisionError:
        return -1

    if recommendation == YES or recommendation == NO:
        # If everyone in agreement return Yes/No
        return recommendation
    else:
        return MAYBE

opinions

opinions()
Source code in hypha/apply/review/models.py
def opinions(self):
    return ReviewOpinion.objects.filter(review__id__in=self.values_list("id"))

Review

Bases: ReviewFormFieldsMixin, BaseStreamForm, AccessFormData, Model

stream_file_class class-attribute instance-attribute

stream_file_class = SubmissionStreamFieldFile

storage_class class-attribute instance-attribute

storage_class = PrivateStorage

raw_data property

raw_data

question_field_ids property

question_field_ids

file_field_ids property

file_field_ids

question_text_field_ids property

question_text_field_ids

first_group_question_text_field_ids property

first_group_question_text_field_ids

raw_fields property

raw_fields

fields property

fields

named_blocks property

named_blocks

normal_blocks property

normal_blocks

group_toggle_blocks property

group_toggle_blocks

first_group_normal_text_blocks property

first_group_normal_text_blocks

submission_form_class class-attribute instance-attribute

submission_form_class = PageStreamBaseForm

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

form_fields class-attribute instance-attribute

form_fields = StreamField(ReviewCustomFormFieldsBlock(), use_json_field=True)

score_fields property

score_fields

score_fields_without_text property

score_fields_without_text

recommendation_field property

recommendation_field

visibility_field property

visibility_field

comment_field property

comment_field

submission class-attribute instance-attribute

submission = ForeignKey('funds.ApplicationSubmission', on_delete=CASCADE, related_name='reviews')

revision class-attribute instance-attribute

revision = ForeignKey('funds.ApplicationRevision', on_delete=SET_NULL, related_name='reviews', null=True)

author class-attribute instance-attribute

author = OneToOneField('funds.AssignedReviewers', related_name='review', on_delete=CASCADE)

form_data class-attribute instance-attribute

form_data = JSONField(default=dict, encoder=DjangoJSONEncoder)

recommendation class-attribute instance-attribute

recommendation = IntegerField(verbose_name=gettext_lazy('Recommendation'), choices=RECOMMENDATION_CHOICES, default=0)

score class-attribute instance-attribute

score = DecimalField(max_digits=10, decimal_places=1, default=0)

is_draft class-attribute instance-attribute

is_draft = BooleanField(default=False, verbose_name=gettext_lazy('Draft'))

created_at class-attribute instance-attribute

created_at = DateTimeField(verbose_name=gettext_lazy('Creation time'), auto_now_add=True)

updated_at class-attribute instance-attribute

updated_at = DateTimeField(verbose_name=gettext_lazy('Update time'), auto_now=True)

visibility class-attribute instance-attribute

visibility = CharField(verbose_name=gettext_lazy('Visibility'), choices=items(), default=PRIVATE, max_length=10)

drupal_id class-attribute instance-attribute

drupal_id = IntegerField(null=True, blank=True, editable=False)

objects class-attribute instance-attribute

objects = as_manager()

outcome property

outcome

get_score_display property

get_score_display

for_latest property

for_latest

Meta

unique_together class-attribute instance-attribute
unique_together = ('author', 'submission')

stream_file classmethod

stream_file(instance, field, file)
Source code in hypha/apply/funds/models/mixins.py
@classmethod
def stream_file(cls, instance, field, file):
    if not file:
        return []
    if isinstance(file, cls.stream_file_class):
        return file
    if isinstance(file, File):
        return cls.stream_file_class(
            instance, field, file, name=file.name, storage=cls.storage_class()
        )

    if isinstance(file, PlaceholderUploadedFile):
        return cls.stream_file_class(
            instance,
            field,
            None,
            name=file.file_id,
            filename=file.name,
            storage=cls.storage_class(),
        )

    # This fixes a backwards compatibility issue with #507
    # Once every application has been re-saved it should be possible to remove it
    if "path" in file:
        file["filename"] = file["name"]
        file["name"] = file["path"]
    return cls.stream_file_class(
        instance,
        field,
        None,
        name=file["name"],
        filename=file.get("filename"),
        storage=cls.storage_class(),
    )

process_file classmethod

process_file(instance, field, file)
Source code in hypha/apply/funds/models/mixins.py
@classmethod
def process_file(cls, instance, field, file):
    if isinstance(file, list):
        return [cls.stream_file(instance, field, f) for f in file]
    else:
        return cls.stream_file(instance, field, file)

process_file_data

process_file_data(data)
Source code in hypha/apply/funds/models/mixins.py
def process_file_data(self, data):
    for field in self.form_fields:
        if isinstance(field.block, UploadableMediaBlock):
            file = self.process_file(self, field, data.get(field.id, []))
            try:
                file.save()
            except (AttributeError, FileNotFoundError):
                try:
                    for f in file:
                        f.save()
                except FileNotFoundError:
                    pass
            self.form_data[field.id] = file

extract_files

extract_files()
Source code in hypha/apply/funds/models/mixins.py
def extract_files(self):
    files = {}
    for field in self.form_fields:
        if isinstance(field.block, UploadableMediaBlock):
            files[field.id] = self.data(field.id) or []
            self.form_data.pop(field.id, None)
    return files

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

deserialised_data classmethod

deserialised_data(instance, data, form_fields)
Source code in hypha/apply/funds/models/mixins.py
@classmethod
def deserialised_data(cls, instance, data, form_fields):
    # Converts the file dicts into actual file objects
    data = 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"]]
        if isinstance(block, UploadableMediaBlock):
            field_id = field_data.get("id")
            if field_id:
                field = form_fields[i]
                file = data.get(field_id, [])
                data[field_id] = cls.process_file(instance, field, file)
    return data

get_definitive_id

get_definitive_id(id)
Source code in hypha/apply/funds/models/mixins.py
def get_definitive_id(self, id):
    if id in self.named_blocks:
        return self.named_blocks[id]
    return id

field

field(id)
Source code in hypha/apply/funds/models/mixins.py
def field(self, id):
    definitive_id = self.get_definitive_id(id)
    try:
        return self.raw_fields[definitive_id]
    except KeyError:
        raise UnusedFieldException(id) from None

data

data(id)
Source code in hypha/apply/funds/models/mixins.py
def data(self, id):
    definitive_id = self.get_definitive_id(id)
    try:
        return self.raw_data[definitive_id]
    except KeyError:
        # We have most likely progressed application forms so the data isn't in form_data
        return None

get_serialize_multi_inputs_answer

get_serialize_multi_inputs_answer(field)
Source code in hypha/apply/funds/models/mixins.py
def get_serialize_multi_inputs_answer(self, field):
    number_of_inputs = field.value.get("number_of_inputs")
    answers = [self.data(field.id + "_" + str(i)) for i in range(number_of_inputs)]
    data = ", ".join(filter(None, answers))
    return data

serialize

serialize(field_id)
Source code in hypha/apply/funds/models/mixins.py
def serialize(self, field_id):
    field = self.field(field_id)
    if isinstance(field.block, MultiInputCharFieldBlock):
        data = self.get_serialize_multi_inputs_answer(field)
    else:
        data = self.data(field_id)
    return field.render(
        context={
            "serialize": True,
            "data": data,
        }
    )

get_multi_inputs_answer

get_multi_inputs_answer(field, include_question=False)
Source code in hypha/apply/funds/models/mixins.py
def get_multi_inputs_answer(self, field, include_question=False):
    number_of_inputs = field.value.get("number_of_inputs")
    answers = [self.data(field.id + "_" + str(i)) for i in range(number_of_inputs)]

    render_data = [
        field.render(
            context={
                "data": answer,
                "include_question": include_question if i == 0 else False,
            }
        )
        for i, answer in enumerate(filter(None, answers))
    ]
    return "".join(render_data).replace("</section>", "") + "</section>"

render_answer

render_answer(field_id, include_question=False)
Source code in hypha/apply/funds/models/mixins.py
def render_answer(self, field_id, include_question=False):
    try:
        field = self.field(field_id)
    except UnusedFieldException:
        return "-"
    if isinstance(field.block, MultiInputCharFieldBlock):
        render_data = self.get_multi_inputs_answer(field, include_question)
        return render_data
    else:
        data = self.data(field_id)
    # Some migrated content have empty address.
    if not data:
        return field.render(
            context={"data": "", "include_question": include_question}
        )
    return field.render(
        context={"data": data, "include_question": include_question}
    )

render_answers

render_answers()
Source code in hypha/apply/funds/models/mixins.py
def render_answers(self):
    # Returns a list of the rendered answers
    return [
        self.render_answer(field_id, include_question=True)
        for field_id in self.normal_blocks
    ]

render_first_group_text_answers

render_first_group_text_answers()
Source code in hypha/apply/funds/models/mixins.py
def render_first_group_text_answers(self):
    return [
        self.render_answer(field_id, include_question=True)
        for field_id in self.first_group_normal_text_blocks
    ]

render_text_blocks_answers

render_text_blocks_answers()
Source code in hypha/apply/funds/models/mixins.py
def render_text_blocks_answers(self):
    # Returns a list of the rendered answers of type text
    return [
        self.render_answer(field_id, include_question=True)
        for field_id in self.question_text_field_ids
        if field_id not in self.named_blocks
    ]

output_answers

output_answers()
Source code in hypha/apply/funds/models/mixins.py
def output_answers(self):
    # Returns a safe string of the rendered answers
    return mark_safe("".join(self.render_answers()))

output_text_answers

output_text_answers()
Source code in hypha/apply/funds/models/mixins.py
def output_text_answers(self):
    return mark_safe("".join(self.render_text_blocks_answers()))

output_first_group_text_answers

output_first_group_text_answers()
Source code in hypha/apply/funds/models/mixins.py
def output_first_group_text_answers(self):
    return mark_safe("".join(self.render_first_group_text_answers()))

get_answer_from_label

get_answer_from_label(label)
Source code in hypha/apply/funds/models/mixins.py
def get_answer_from_label(self, label):
    for field_id in self.question_text_field_ids:
        if field_id not in self.named_blocks:
            question_field = self.serialize(field_id)
            if label.lower() in question_field["question"].lower():
                if isinstance(question_field["answer"], str):
                    answer = question_field["answer"]
                else:
                    answer = ",".join(question_field["answer"])
                if answer and not answer == "N":
                    return answer
    return None

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_defined_fields

get_defined_fields()
Source code in hypha/apply/stream_forms/models.py
def get_defined_fields(self):
    return self.form_fields

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_comments_display

get_comments_display(include_question=True)
Source code in hypha/apply/review/models.py
def get_comments_display(self, include_question=True):
    return self.render_answer(
        self.comment_field.id, include_question=include_question
    )

get_absolute_url

get_absolute_url()
Source code in hypha/apply/review/models.py
def get_absolute_url(self):
    return reverse(
        "apply:submissions:reviews:review",
        args=(
            self.submission.pk,
            self.id,
        ),
    )

get_compare_url

get_compare_url()
Source code in hypha/apply/review/models.py
def get_compare_url(self):
    return self.revision.get_compare_url_to_latest()

reviewer_visibility

reviewer_visibility()
Source code in hypha/apply/review/models.py
@cached_property
def reviewer_visibility(self):
    return self.visibility == REVIEWER

is_updated

is_updated()
Source code in hypha/apply/review/models.py
@cached_property
def is_updated(self):
    # Only compare dates, not time.
    return self.created_at.date() < self.updated_at.date()

ReviewOpinion

Bases: Model

review class-attribute instance-attribute

review = ForeignKey(Review, on_delete=CASCADE, related_name='opinions')

author class-attribute instance-attribute

author = ForeignKey('funds.AssignedReviewers', related_name='opinions', on_delete=CASCADE)

opinion class-attribute instance-attribute

opinion = IntegerField(choices=OPINION_CHOICES)

opinion_display property

opinion_display

Meta

unique_together class-attribute instance-attribute
unique_together = ('author', 'review')