Skip to content

Report

hypha.apply.projects.models.report

ReportQueryset

Bases: QuerySet

done

done()
Source code in hypha/apply/projects/models/report.py
def done(self):
    return self.filter(
        Q(current__isnull=False) | Q(skipped=True),
    )

to_do

to_do()
Source code in hypha/apply/projects/models/report.py
def to_do(self):
    today = timezone.now().date()
    return self.filter(
        current__isnull=True,
        skipped=False,
        end_date__lt=today,
    ).order_by("end_date")

any_very_late

any_very_late()
Source code in hypha/apply/projects/models/report.py
def any_very_late(self):
    two_weeks_ago = timezone.now().date() - relativedelta(weeks=2)
    return self.to_do().filter(end_date__lte=two_weeks_ago)

submitted

submitted()
Source code in hypha/apply/projects/models/report.py
def submitted(self):
    return self.filter(current__isnull=False)

for_table

for_table()
Source code in hypha/apply/projects/models/report.py
def for_table(self):
    Project = apps.get_model("application_projects", "Project")
    return self.annotate(
        last_end_date=Subquery(
            Report.objects.filter(
                project=OuterRef("project_id"), end_date__lt=OuterRef("end_date")
            ).values("end_date")[:1]
        ),
        project_start_date=Subquery(
            Project.objects.filter(
                pk=OuterRef("project_id"),
            )
            .with_start_date()
            .values("start")[:1]
        ),
        start=Case(
            When(
                last_end_date__isnull=False,
                # Expression Wrapper doesn't cast the calculated object
                # Use cast to get an actual date object
                then=Cast(
                    ExpressionWrapper(
                        F("last_end_date") + datetime.timedelta(days=1),
                        output_field=models.DateTimeField(),
                    ),
                    models.DateField(),
                ),
            ),
            default=F("project_start_date"),
            output_field=models.DateField(),
        ),
    )

Report

Bases: Model

skipped class-attribute instance-attribute

skipped = BooleanField(default=False)

end_date class-attribute instance-attribute

end_date = DateField()

project class-attribute instance-attribute

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

submitted class-attribute instance-attribute

submitted = DateTimeField(null=True)

notified class-attribute instance-attribute

notified = DateTimeField(null=True)

current class-attribute instance-attribute

current = OneToOneField('ReportVersion', on_delete=CASCADE, related_name='live_for_report', null=True)

draft class-attribute instance-attribute

draft = OneToOneField('ReportVersion', on_delete=CASCADE, related_name='draft_for_report', null=True)

objects class-attribute instance-attribute

objects = as_manager()

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

previous property

previous

next property

next

past_due property

past_due

is_very_late property

is_very_late

can_submit property

can_submit

submitted_date property

submitted_date

Meta

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

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/report.py
def get_absolute_url(self):
    return reverse("apply:projects:reports:detail", kwargs={"pk": self.pk})

start_date

start_date()
Source code in hypha/apply/projects/models/report.py
@cached_property
def start_date(self):
    last_report = self.project.reports.filter(end_date__lt=self.end_date).first()
    if last_report:
        return last_report.end_date + relativedelta(days=1)

    return self.project.start_date

ReportVersion

Bases: Model

report class-attribute instance-attribute

report = ForeignKey('Report', on_delete=CASCADE, related_name='versions')

submitted class-attribute instance-attribute

submitted = DateTimeField()

public_content class-attribute instance-attribute

public_content = TextField()

private_content class-attribute instance-attribute

private_content = TextField()

draft class-attribute instance-attribute

draft = BooleanField()

author class-attribute instance-attribute

author = ForeignKey(AUTH_USER_MODEL, on_delete=SET_NULL, related_name='reports', null=True)

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

ReportPrivateFiles

Bases: Model

report class-attribute instance-attribute

report = ForeignKey('ReportVersion', on_delete=CASCADE, related_name='files')

document class-attribute instance-attribute

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

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

filename property

filename

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/report.py
def get_absolute_url(self):
    return reverse(
        "apply:projects:reports:document",
        kwargs={"pk": self.report.report_id, "file_pk": self.pk},
    )

ReportConfig

Bases: Model

Persists configuration about the reporting schedule etc

WEEK class-attribute instance-attribute

WEEK = gettext_lazy('week')

MONTH class-attribute instance-attribute

MONTH = gettext_lazy('month')

YEAR class-attribute instance-attribute

YEAR = gettext_lazy('year')

FREQUENCY_CHOICES class-attribute instance-attribute

FREQUENCY_CHOICES = [(WEEK, gettext_lazy('Weeks')), (MONTH, gettext_lazy('Months')), (YEAR, gettext_lazy('Years'))]

project class-attribute instance-attribute

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

schedule_start class-attribute instance-attribute

schedule_start = DateField(null=True)

occurrence class-attribute instance-attribute

occurrence = PositiveSmallIntegerField(default=1)

frequency class-attribute instance-attribute

frequency = CharField(choices=FREQUENCY_CHOICES, default=MONTH, max_length=6)

disable_reporting class-attribute instance-attribute

disable_reporting = BooleanField(default=False)

does_not_repeat class-attribute instance-attribute

does_not_repeat = BooleanField(default=False)

get_frequency_display

get_frequency_display()
Source code in hypha/apply/projects/models/report.py
def get_frequency_display(self):
    if self.disable_reporting:
        return _("Reporting Disabled")
    if self.does_not_repeat:
        last_report = self.last_report()
        if last_report:
            return _(
                "One time, that already has reported on {date}".format(
                    date=last_report.end_date.strftime("%d %B, %Y")
                )
            )
        return _(
            "One time on {date}".format(
                date=self.schedule_start.strftime("%d %B, %Y")
            )
        )
    next_report = self.current_due_report()

    if self.frequency == self.YEAR:
        if self.schedule_start and self.schedule_start.day == 31:
            day_of_month = _("last day")
            month = self.schedule_start.strftime("%B")
        else:
            day_of_month = ordinal(next_report.end_date.day)
            month = next_report.end_date.strftime("%B")
        if self.occurrence == 1:
            return _("Once a year on {month} {day}").format(
                day=day_of_month, month=month
            )
        return _("Every {occurrence} years on {month} {day}").format(
            occurrence=self.occurrence, day=day_of_month, month=month
        )

    if self.frequency == self.MONTH:
        if self.schedule_start and self.schedule_start.day == 31:
            day_of_month = _("last day")
        else:
            day_of_month = ordinal(next_report.end_date.day)
        if self.occurrence == 1:
            return _("Once a month on the {day}").format(day=day_of_month)
        return _("Every {occurrence} months on the {day}").format(
            occurrence=self.occurrence, day=day_of_month
        )

    weekday = next_report.end_date.strftime("%A")

    if self.occurrence == 1:
        return _("Once a week on {weekday}").format(weekday=weekday)
    return _("Every {occurrence} weeks on {weekday}").format(
        occurrence=self.occurrence, weekday=weekday
    )

is_up_to_date

is_up_to_date()
Source code in hypha/apply/projects/models/report.py
def is_up_to_date(self):
    return len(self.project.reports.to_do()) == 0

outstanding_reports

outstanding_reports()
Source code in hypha/apply/projects/models/report.py
def outstanding_reports(self):
    return len(self.project.reports.to_do())

has_very_late_reports

has_very_late_reports()
Source code in hypha/apply/projects/models/report.py
def has_very_late_reports(self):
    return self.project.reports.any_very_late()

past_due_reports

past_due_reports()
Source code in hypha/apply/projects/models/report.py
def past_due_reports(self):
    return self.project.reports.to_do()

last_report

last_report()
Source code in hypha/apply/projects/models/report.py
def last_report(self):
    today = timezone.now().date()
    # Get the most recent report that was either:
    # - due by today and not submitted
    # - was skipped but due after today
    # - was submitted but due after today
    return self.project.reports.filter(
        Q(end_date__lt=today) | Q(skipped=True) | Q(submitted__isnull=False)
    ).first()

current_due_report

current_due_report()
Source code in hypha/apply/projects/models/report.py
def current_due_report(self):
    if self.disable_reporting:
        return None

    # Project not started - no reporting required
    if not self.project.start_date:
        return None

    today = timezone.now().date()

    last_report = self.last_report()

    schedule_date = self.schedule_start or self.project.start_date

    if last_report:
        # Frequency is one time and last report exists - no reporting required anymore
        if self.does_not_repeat:
            return None

        if last_report.end_date < schedule_date:
            # reporting schedule changed schedule_start is now the next report date
            next_due_date = schedule_date
        else:
            # we've had a report since the schedule date so base next deadline from the report
            next_due_date = self.next_date(last_report.end_date)
    else:
        # first report required
        if self.schedule_start and self.schedule_start >= today:
            # Schedule changed since project inception
            next_due_date = self.schedule_start
        else:
            # schedule_start is the first day the project so the "last" period
            # ended one day before that. If date is in past we required report now
            if self.does_not_repeat:
                next_due_date = today
            else:
                next_due_date = max(
                    self.next_date(schedule_date - relativedelta(days=1)),
                    today,
                )

    report, _ = self.project.reports.update_or_create(
        project=self.project,
        current__isnull=True,
        skipped=False,
        end_date__gte=today,
        defaults={"end_date": next_due_date},
    )
    return report

next_date

next_date(last_date)
Source code in hypha/apply/projects/models/report.py
def next_date(self, last_date):
    delta_frequency = self.frequency + "s"
    delta = relativedelta(**{delta_frequency: self.occurrence})
    next_date = last_date + delta
    return next_date

report_path

report_path(instance, filename)
Source code in hypha/apply/projects/models/report.py
def report_path(instance, filename):
    return (
        f"reports/{instance.report.report_id}/version/{instance.report_id}/{filename}"
    )