Skip to content

Project

hypha.apply.projects.models.project

logger module-attribute

logger = getLogger(__name__)

PROJECT_ACTION_MESSAGE_TAG module-attribute

PROJECT_ACTION_MESSAGE_TAG = 'project_action_message'

APPROVE module-attribute

APPROVE = 'approve'

REQUEST_CHANGE module-attribute

REQUEST_CHANGE = 'request_change'

PAF_STATUS_CHOICES module-attribute

PAF_STATUS_CHOICES = ((APPROVE, 'Approve'), (REQUEST_CHANGE, 'Request changes or more information'))

DRAFT module-attribute

DRAFT = 'draft'

INTERNAL_APPROVAL module-attribute

INTERNAL_APPROVAL = 'internal_approval'

CONTRACTING module-attribute

CONTRACTING = 'contracting'

INVOICING_AND_REPORTING module-attribute

INVOICING_AND_REPORTING = 'invoicing_and_reporting'

CLOSING module-attribute

CLOSING = 'closing'

COMPLETE module-attribute

COMPLETE = 'complete'

PROJECT_STATUS_CHOICES module-attribute

PROJECT_STATUS_CHOICES = [(DRAFT, gettext_lazy('Draft')), (INTERNAL_APPROVAL, gettext_lazy('Internal approval')), (CONTRACTING, gettext_lazy('Contracting')), (INVOICING_AND_REPORTING, gettext_lazy('Invoicing and reporting')), (CLOSING, gettext_lazy('Closing')), (COMPLETE, gettext_lazy('Complete'))]

PROJECT_PUBLIC_STATUSES module-attribute

PROJECT_PUBLIC_STATUSES = [(DRAFT, gettext_lazy('Draft')), (INTERNAL_APPROVAL, format(ORG_SHORT_NAME)), (CONTRACTING, gettext_lazy('Contracting')), (INVOICING_AND_REPORTING, gettext_lazy('Invoicing and reporting')), (CLOSING, gettext_lazy('Closing')), (COMPLETE, gettext_lazy('Complete'))]

ProjectQuerySet

Bases: QuerySet

active

active()
Source code in hypha/apply/projects/models/project.py
def active(self):
    # Projects that are not finished.
    return self.exclude(status=COMPLETE)

in_progress

in_progress()
Source code in hypha/apply/projects/models/project.py
def in_progress(self):
    # Projects that users need to interact with, submitting reports or payment request.
    return self.filter(
        status__in=(
            INVOICING_AND_REPORTING,
            CLOSING,
        )
    )

complete

complete()
Source code in hypha/apply/projects/models/project.py
def complete(self):
    return self.filter(status=COMPLETE)

in_contracting

in_contracting()
Source code in hypha/apply/projects/models/project.py
def in_contracting(self):
    return self.filter(status=CONTRACTING)

internal_approval

internal_approval()
Source code in hypha/apply/projects/models/project.py
def internal_approval(self):
    return self.filter(
        status=INTERNAL_APPROVAL,
    )

by_end_date

by_end_date(desc=False)
Source code in hypha/apply/projects/models/project.py
def by_end_date(self, desc=False):
    order = getattr(F("proposed_end"), "desc" if desc else "asc")(nulls_last=True)

    return self.order_by(order)

with_amount_paid

with_amount_paid()
Source code in hypha/apply/projects/models/project.py
def with_amount_paid(self):
    return self.annotate(
        amount_paid=Coalesce(
            Sum("invoices__paid_value"),
            Value(0),
            output_field=models.DecimalField(),
        ),
    )

with_last_payment

with_last_payment()
Source code in hypha/apply/projects/models/project.py
def with_last_payment(self):
    return self.annotate(
        last_payment_request=Max(
            "invoices__requested_at", output_field=models.DateTimeField()
        ),
    )

with_outstanding_reports

with_outstanding_reports()
Source code in hypha/apply/projects/models/project.py
def with_outstanding_reports(self):
    Report = apps.get_model("application_projects", "Report")
    return self.annotate(
        outstanding_reports=Subquery(
            Report.objects.filter(
                project=OuterRef("pk"),
            )
            .to_do()
            .order_by()
            .values("project")
            .annotate(
                count=Count("pk"),
            )
            .values("count"),
            output_field=models.IntegerField(),
        )
    )

with_start_date

with_start_date()
Source code in hypha/apply/projects/models/project.py
def with_start_date(self):
    return self.annotate(
        start=Cast(
            Subquery(
                Contract.objects.filter(
                    project=OuterRef("pk"),
                )
                .approved()
                .order_by("approved_at")
                .values("approved_at")[:1]
            ),
            models.DateField(),
        )
    )

for_table

for_table()
Source code in hypha/apply/projects/models/project.py
def for_table(self):
    return (
        self.with_amount_paid()
        .with_last_payment()
        .with_outstanding_reports()
        .select_related(
            "report_config",
            "submission__page",
            "lead",
        )
    )

Project

Bases: 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

lead class-attribute instance-attribute

lead = ForeignKey(AUTH_USER_MODEL, null=True, on_delete=SET_NULL, related_name='lead_projects')

submission class-attribute instance-attribute

submission = OneToOneField('funds.ApplicationSubmission', on_delete=CASCADE)

user class-attribute instance-attribute

user = ForeignKey(AUTH_USER_MODEL, on_delete=SET_NULL, null=True, related_name='owned_projects')

title class-attribute instance-attribute

title = TextField()

vendor class-attribute instance-attribute

vendor = ForeignKey('application_projects.Vendor', on_delete=SET_NULL, null=True, blank=True, related_name='projects')

value class-attribute instance-attribute

value = DecimalField(default=0, max_digits=20, decimal_places=2, validators=[MinValueValidator(limit_value=0)])

proposed_start class-attribute instance-attribute

proposed_start = DateTimeField(gettext_lazy('Proposed Start Date'), null=True)

proposed_end class-attribute instance-attribute

proposed_end = DateTimeField(gettext_lazy('Proposed End Date'), null=True)

status class-attribute instance-attribute

status = TextField(choices=PROJECT_STATUS_CHOICES, default=DRAFT)

form_data class-attribute instance-attribute

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

form_fields class-attribute instance-attribute

form_fields = StreamField(ProjectApprovalFormCustomFormFieldsBlock(), null=True, use_json_field=True)

is_locked class-attribute instance-attribute

is_locked = BooleanField(default=False)

user_has_updated_details class-attribute instance-attribute

user_has_updated_details = BooleanField(default=False)

submitted_contract_documents class-attribute instance-attribute

submitted_contract_documents = BooleanField('Submit Contracting Documents', default=False)

activities class-attribute instance-attribute

activities = GenericRelation('activity.Activity', content_type_field='source_content_type', object_id_field='source_object_id', related_query_name='project')

created_at class-attribute instance-attribute

created_at = DateTimeField(auto_now_add=True)

external_projectid class-attribute instance-attribute

external_projectid = CharField(max_length=30, blank=True, help_text='ID of this project at integrated payment service.')

external_project_information class-attribute instance-attribute

external_project_information = JSONField(default=dict, help_text='More details of the project integrated at payment service.')

sent_to_compliance_at class-attribute instance-attribute

sent_to_compliance_at = DateTimeField(null=True)

paf_reviews_meta_data class-attribute instance-attribute

paf_reviews_meta_data = JSONField(default=dict, help_text='Reviewers role and their actions/comments')

objects class-attribute instance-attribute

objects = as_manager()

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

status_display property

status_display

start_date property

start_date

end_date property

end_date

editable property

editable

can_make_approval property

can_make_approval

is_approved_by_all_paf_reviewers property

is_approved_by_all_paf_reviewers

can_update_paf_status property

can_update_paf_status

can_send_for_approval property

can_send_for_approval

Wrapper to expose the pending approval state

We don't want to expose a "Sent for Approval" state to the end User so we infer it from the current status being "Comitted" and the Project being locked.

is_in_progress property

is_in_progress

has_deliverables property

has_deliverables

program_project_id property

program_project_id

Program project id is used to fetch deliverables from IntAcct.

Stored in external_project_information as the first item of referenceno(PONUMBER).

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_address_display

get_address_display()
Source code in hypha/apply/projects/models/project.py
def get_address_display(self):
    try:
        address = json.loads(self.vendor.address)
    except (json.JSONDecodeError, AttributeError):
        return ""
    else:
        return ", ".join(
            address.get(field)
            for field in ADDRESS_FIELDS_ORDER
            if address.get(field)
        )

create_from_submission classmethod

create_from_submission(submission, lead=None)

Create a Project from the given submission.

Returns a new Project or the given ApplicationSubmissions existing Project.

Source code in hypha/apply/projects/models/project.py
@classmethod
def create_from_submission(cls, submission, lead=None):
    """
    Create a Project from the given submission.

    Returns a new Project or the given ApplicationSubmissions existing
    Project.
    """
    if not settings.PROJECTS_ENABLED:
        logging.error(
            f"Tried to create a Project for Submission ID={submission.id} while projects are disabled"
        )
        return None

    # OneToOne relations on the targetted model cannot be accessed without
    # an exception when the relation doesn't exist (is None).  Since we
    # want to fail fast here, we can use hasattr instead.
    if hasattr(submission, "project"):
        return submission.project

    # See if there is a form field named "legal name", if not use user name.
    legal_name = (
        submission.get_answer_from_label("legal name") or submission.user.full_name
    )
    vendor, _ = Vendor.objects.get_or_create(user=submission.user)
    vendor.name = legal_name
    vendor.address = submission.form_data.get("address", "")
    vendor.save()
    return Project.objects.create(
        submission=submission,
        user=submission.user,
        title=submission.title,
        vendor=vendor,
        lead=lead if lead else None,
        value=submission.form_data.get("value", 0),
    )

paid_value

paid_value()
Source code in hypha/apply/projects/models/project.py
def paid_value(self):
    return self.invoices.paid_value()

unpaid_value

unpaid_value()
Source code in hypha/apply/projects/models/project.py
def unpaid_value(self):
    return self.invoices.unpaid_value()

clean

clean()
Source code in hypha/apply/projects/models/project.py
def clean(self):
    if self.proposed_start is None:
        return

    if self.proposed_end is None:
        return

    if self.proposed_start > self.proposed_end:
        raise ValidationError(
            _("Proposed End Date must be after Proposed Start Date")
        )

save

save(*args, **kwargs)
Source code in hypha/apply/projects/models/project.py
def save(self, *args, **kwargs):
    creating = not self.pk

    if creating:
        files = self.extract_files()
    else:
        self.process_file_data(self.form_data)

    super().save(*args, **kwargs)

    if creating:
        self.process_file_data(files)

editable_by

editable_by(user)
Source code in hypha/apply/projects/models/project.py
def editable_by(self, user):
    if self.editable:
        # Approver can edit it when they are approving
        if self.can_make_approval:
            if user.is_finance or user.is_approver or user.is_contracting:
                return True

        # Lead can make changes to the project
        if user == self.lead:
            return True

        # Staff can edit project
        if user.is_apply_staff:
            return True
    return False

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/project.py
def get_absolute_url(self):
    return reverse("apply:projects:detail", args=[self.id])

can_request_funding

can_request_funding()

Should we show this Project's funding block?

Source code in hypha/apply/projects/models/project.py
def can_request_funding(self):
    """
    Should we show this Project's funding block?
    """
    return self.status in (CLOSING, INVOICING_AND_REPORTING)

ProjectSOW

Bases: 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

project class-attribute instance-attribute

project = OneToOneField(Project, related_name='sow', on_delete=CASCADE)

form_data class-attribute instance-attribute

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

form_fields class-attribute instance-attribute

form_fields = StreamField(ProjectApprovalFormCustomFormFieldsBlock(), null=True, use_json_field=True)

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),
    )

ProjectBaseStreamForm

Bases: BaseStreamForm, Model

submission_form_class class-attribute instance-attribute

submission_form_class = PageStreamBaseForm

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

name class-attribute instance-attribute

name = CharField(max_length=255)

form_fields class-attribute instance-attribute

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

panels class-attribute instance-attribute

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

Meta

abstract class-attribute instance-attribute
abstract = True

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_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),
    )

ProjectApprovalForm

ProjectSOWForm

PAFReviewersRole

Bases: Orderable, ClusterableModel

label class-attribute instance-attribute

label = CharField(max_length=200)

user_roles class-attribute instance-attribute

user_roles = ParentalManyToManyField(Group, verbose_name=gettext_lazy('user groups'), help_text=gettext_lazy("Only selected group's users will be listed for this PAFReviewerRole"), related_name='paf_reviewers_roles')

page class-attribute instance-attribute

page = ParentalKey('ProjectSettings', related_name='paf_reviewers_roles')

panels class-attribute instance-attribute

panels = [FieldPanel('label'), FieldPanel('user_roles', widget=CheckboxSelectMultiple)]

ProjectSettings

Bases: BaseSiteSetting, ClusterableModel

contracting_gp_email class-attribute instance-attribute

contracting_gp_email = TextField('Contracting Group Email', null=True, blank=True)

finance_gp_email class-attribute instance-attribute

finance_gp_email = TextField('Finance Group Email', null=True, blank=True)

staff_gp_email class-attribute instance-attribute

staff_gp_email = TextField('Staff Group Email', null=True, blank=True)

vendor_setup_required class-attribute instance-attribute

vendor_setup_required = BooleanField(default=True)

paf_approval_sequential class-attribute instance-attribute

paf_approval_sequential = BooleanField(default=True, help_text='Uncheck it to approve PAF parallely')

panels class-attribute instance-attribute

panels = [FieldPanel('staff_gp_email'), FieldPanel('contracting_gp_email'), FieldPanel('finance_gp_email'), FieldPanel('vendor_setup_required'), MultiFieldPanel([FieldPanel('paf_approval_sequential', heading='Approve PAF Sequentially'), InlinePanel('paf_reviewers_roles', label=gettext_lazy('PAF Reviewers Roles'))], heading=gettext_lazy('PAF Reviewers Roles'))]

PAFApprovals

Bases: Model

project class-attribute instance-attribute

project = ForeignKey('Project', on_delete=CASCADE, related_name='paf_approvals')

paf_reviewer_role class-attribute instance-attribute

paf_reviewer_role = ForeignKey('PAFReviewersRole', on_delete=CASCADE, related_name='paf_approvals')

user class-attribute instance-attribute

user = ForeignKey(AUTH_USER_MODEL, on_delete=SET_NULL, null=True, blank=True, related_name='paf_approvals')

approved class-attribute instance-attribute

approved = BooleanField(default=False)

created_at class-attribute instance-attribute

created_at = DateTimeField(auto_now_add=True)

updated_at class-attribute instance-attribute

updated_at = DateTimeField()

approved_at class-attribute instance-attribute

approved_at = DateTimeField(null=True, blank=True)

Meta

unique_together class-attribute instance-attribute
unique_together = ['project', 'paf_reviewer_role']
ordering class-attribute instance-attribute
ordering = ['paf_reviewer_role__sort_order']

save

save(*args, **kwargs)
Source code in hypha/apply/projects/models/project.py
def save(self, *args, **kwargs):
    self.updated_at = timezone.now()
    return super().save(*args, **kwargs)

ContractQuerySet

Bases: QuerySet

approved

approved()
Source code in hypha/apply/projects/models/project.py
def approved(self):
    return self.filter(signed_by_applicant=True, approver__isnull=False)

Contract

Bases: Model

approver class-attribute instance-attribute

approver = ForeignKey(AUTH_USER_MODEL, null=True, on_delete=SET_NULL, related_name='contracts')

project class-attribute instance-attribute

project = ForeignKey('Project', on_delete=CASCADE, related_name='contracts')

file class-attribute instance-attribute

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

signed_by_applicant class-attribute instance-attribute

signed_by_applicant = BooleanField('Counter Signed?', default=False)

uploaded_by_contractor_at class-attribute instance-attribute

uploaded_by_contractor_at = DateTimeField(null=True)

uploaded_by_applicant_at class-attribute instance-attribute

uploaded_by_applicant_at = DateTimeField(null=True)

created_at class-attribute instance-attribute

created_at = DateTimeField(auto_now_add=True)

approved_at class-attribute instance-attribute

approved_at = DateTimeField(null=True)

updated_at class-attribute instance-attribute

updated_at = DateTimeField(null=True)

objects class-attribute instance-attribute

objects = as_manager()

state property

state

save

save(*args, **kwargs)
Source code in hypha/apply/projects/models/project.py
def save(self, *args, **kwargs):
    self.updated_at = timezone.now()
    return super().save(*args, **kwargs)

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/project.py
def get_absolute_url(self):
    return reverse("apply:projects:contract", args=[self.project.pk, self.pk])

PacketFile

Bases: Model

category class-attribute instance-attribute

category = ForeignKey('DocumentCategory', null=True, on_delete=CASCADE, related_name='packet_files')

project class-attribute instance-attribute

project = ForeignKey('Project', on_delete=CASCADE, related_name='packet_files')

title class-attribute instance-attribute

title = TextField()

document class-attribute instance-attribute

document = FileField(upload_to=document_path, storage=PrivateStorage())

created_at class-attribute instance-attribute

created_at = DateField(auto_now_add=True, null=True)

Meta

ordering class-attribute instance-attribute
ordering = ('-created_at')

get_remove_form

get_remove_form()

Get an instantiated RemoveDocumentForm with this class as instance.

This allows us to build instances of the RemoveDocumentForm for each instance of PacketFile in the supporting documents template. The standard Delegated View flow makes it difficult to create these forms in the view or template.

Source code in hypha/apply/projects/models/project.py
def get_remove_form(self):
    """
    Get an instantiated RemoveDocumentForm with this class as `instance`.

    This allows us to build instances of the RemoveDocumentForm for each
    instance of PacketFile in the supporting documents template.  The
    standard Delegated View flow makes it difficult to create these forms
    in the view or template.
    """
    from ..forms import RemoveDocumentForm

    return RemoveDocumentForm(instance=self)

ContractPacketFile

Bases: Model

category class-attribute instance-attribute

category = ForeignKey('ContractDocumentCategory', null=True, on_delete=CASCADE, related_name='contract_packet_files')

project class-attribute instance-attribute

project = ForeignKey('Project', on_delete=CASCADE, related_name='contract_packet_files')

title class-attribute instance-attribute

title = TextField()

document class-attribute instance-attribute

document = FileField(upload_to=contract_document_path, storage=PrivateStorage())

created_at class-attribute instance-attribute

created_at = DateField(auto_now_add=True, null=True)

get_remove_form

get_remove_form()

Get an instantiated RemoveContractDocumentForm with this class as instance.

This allows us to build instances of the RemoveContractDocumentForm for each instance of ContractPacketFile in the contracting documents template. The standard Delegated View flow makes it difficult to create these forms in the view or template.

Source code in hypha/apply/projects/models/project.py
def get_remove_form(self):
    """
    Get an instantiated RemoveContractDocumentForm with this class as `instance`.

    This allows us to build instances of the RemoveContractDocumentForm for each
    instance of ContractPacketFile in the contracting documents template.  The
    standard Delegated View flow makes it difficult to create these forms
    in the view or template.
    """
    from ..forms import RemoveContractDocumentForm

    return RemoveContractDocumentForm(instance=self)

DocumentCategory

Bases: Model

name class-attribute instance-attribute

name = CharField(max_length=254)

recommended_minimum class-attribute instance-attribute

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

required class-attribute instance-attribute

required = BooleanField(default=False)

template class-attribute instance-attribute

template = FileField(upload_to=document_template_path, storage=PrivateStorage(), blank=True, null=True)

panels class-attribute instance-attribute

panels = [FieldPanel('name'), FieldPanel('required'), FieldPanel('template')]

Meta

ordering class-attribute instance-attribute
ordering = ('-required', 'name')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = 'Project Document Categories'

ContractDocumentCategory

Bases: Model

name class-attribute instance-attribute

name = CharField(max_length=254)

recommended_minimum class-attribute instance-attribute

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

required class-attribute instance-attribute

required = BooleanField(default=True)

template class-attribute instance-attribute

template = FileField(upload_to=contract_document_template_path, storage=PrivateStorage(), blank=True, null=True)

panels class-attribute instance-attribute

panels = [FieldPanel('name'), FieldPanel('required'), FieldPanel('template')]

Meta

ordering class-attribute instance-attribute
ordering = ('-required', 'name')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = 'Contract Document Categories'

Deliverable

Bases: Model

external_id class-attribute instance-attribute

external_id = CharField(max_length=30, blank=True, help_text='ID of this deliverable at integrated payment service.')

name class-attribute instance-attribute

name = TextField()

available_to_invoice class-attribute instance-attribute

available_to_invoice = IntegerField(default=1)

unit_price class-attribute instance-attribute

unit_price = DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])

extra_information class-attribute instance-attribute

extra_information = JSONField(default=dict, help_text='More details of the deliverable at integrated payment service.')

project class-attribute instance-attribute

project = ForeignKey(Project, null=True, blank=True, on_delete=CASCADE, related_name='deliverables')

contract_path

contract_path(instance, filename)
Source code in hypha/apply/projects/models/project.py
def contract_path(instance, filename):
    return f"projects/{instance.project_id}/contracts/{filename}"

document_path

document_path(instance, filename)
Source code in hypha/apply/projects/models/project.py
def document_path(instance, filename):
    return f"projects/{instance.project_id}/supporting_documents/{filename}"

document_template_path

document_template_path(instance, filename)
Source code in hypha/apply/projects/models/project.py
def document_template_path(instance, filename):
    return f"projects/supporting_documents/{instance.id}/template/{filename}"

contract_document_template_path

contract_document_template_path(instance, filename)
Source code in hypha/apply/projects/models/project.py
def contract_document_template_path(instance, filename):
    return f"projects/contract_documents/{instance.id}/template/{filename}"

contract_document_path

contract_document_path(instance, filename)
Source code in hypha/apply/projects/models/project.py
def contract_document_path(instance, filename):
    return f"projects/{instance.project_id}/contracting_documents/{filename}"

delete_packetfile_file

delete_packetfile_file(sender, instance, **kwargs)
Source code in hypha/apply/projects/models/project.py
@receiver(post_delete, sender=PacketFile)
def delete_packetfile_file(sender, instance, **kwargs):
    # Remove the file and don't save the base model
    instance.document.delete(False)

delete_contractpacketfile_file

delete_contractpacketfile_file(sender, instance, **kwargs)
Source code in hypha/apply/projects/models/project.py
@receiver(post_delete, sender=ContractPacketFile)
def delete_contractpacketfile_file(sender, instance, **kwargs):
    # Remove the file and don't save the base model
    instance.document.delete(False)