Skip to content

models

main.models ¤

Models module for main app.

Classes¤

AnalysisCode ¤

Bases: Model

Analysis code to use during charging.

Functions¤
__str__() ¤

String representation of the Analysis Code object.

Source code in main/models.py
82
83
84
def __str__(self) -> str:
    """String representation of the Analysis Code object."""
    return f"{self.code} - {self.description}"

Capacity ¤

Bases: Model

Proportion of working time that team members are able to work on projects.

Classes¤
Meta ¤

Meta class for the model.

Functions¤
__str__() ¤

String representation of the Capacity object.

Source code in main/models.py
571
572
573
def __str__(self) -> str:
    """String representation of the Capacity object."""
    return f"From {self.start_date}, the capacity of {self.user} is {self.value}."

Department ¤

Bases: Model

Model to manage the departments.

You can find the faculties and potential departments in:

https://www.imperial.ac.uk/faculties-and-departments/

Functions¤
__str__() ¤

String representation of the Department object.

Source code in main/models.py
53
54
55
def __str__(self) -> str:
    """String representation of the Department object."""
    return f"{self.name} - {self.faculty}"

Funding ¤

Bases: Model

Funding associated with a project.

Attributes¤
effort property ¤

Provide the effort in days, calculated based on the budget and daily rate.

Returns:

Type Description
int

The total number of days of effort provided by the funding.

effort_left property ¤

Provide the effort left in days.

Returns:

Type Description
float

The number of days worth of effort left.

funding_left property ¤

Provide the funding left in currency.

Returns:

Type Description
Decimal

The amount of funding left.

monthly_pro_rata_charge property ¤

Calculate the charge per month if the project has Pro-rata charging.

Calculates the number of months between project start and end date regardless of the day of the month so the monthly charge will be the same regardless of the number of days in the month.

project_code property ¤

Provide the project code, containing the cost centre and activity code.

Returns:

Type Description
str | None

The designated project code.

Classes¤
Meta ¤

Meta class for the model.

Functions¤
__str__() ¤

String representation of the Funding object.

Source code in main/models.py
439
440
441
def __str__(self) -> str:
    """String representation of the Funding object."""
    return f"{self.project} - £{self.budget:.2f} - {self.project_code}"
clean() ¤

Ensure that all fields have a value unless the source is 'Internal'.

Source code in main/models.py
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
def clean(self) -> None:
    """Ensure that all fields have a value unless the source is 'Internal'."""
    if self.source == "Internal":
        return super().clean()

    if (
        not self.funding_body
        or not self.cost_centre
        or not self.activity
        or not self.analysis_code
        or not self.expiry_date
    ):
        raise ValidationError(
            "All fields are mandatory except if source is 'Internal'."
        )

    allowed_characters = ["P", "F", "G"]
    if (
        len(self.activity) != 6
        or not self.activity.isalnum()
        or self.activity[0] not in allowed_characters
    ):
        raise ValidationError(
            "Activity code must be 6 alphanumeric characters starting with P, F or"
            " G."
        )

MonthlyCharge ¤

Bases: Model

Monthly charge for a specific project, account and analysis code.

Functions¤
__str__() ¤

String representation of the MonthlyCharge object.

Source code in main/models.py
620
621
622
def __str__(self) -> str:
    """String representation of the MonthlyCharge object."""
    return self.description
clean() ¤

Ensure the charge has valid funding attached and description if Manual.

Source code in main/models.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
def clean(self) -> None:
    """Ensure the charge has valid funding attached and description if Manual."""
    super().clean()
    if not self.funding.expiry_date:
        raise ValidationError("Funding source must have an expiry date.")

    if (
        self.date > self.funding.expiry_date
        or self.funding.funding_left < 0  # After deducting charge amount
    ):
        raise ValidationError(
            "Monthly charge must not exceed the funding date or amount."
        )

    if self.project.charging == "Manual":
        if not self.description:
            raise ValidationError(
                "Line description needed for manual charging method."
            )
    else:
        self.description = (
            f"RSE Project {self.project} ({self.funding.project_code}): "
            f"{self.date.month}/{self.date.year} [rcs-manager@imperial.ac.uk]"
        )

Project ¤

Bases: Model

Software project details.

Attributes¤
days_left property ¤

Provide the days worth of effort left.

Returns:

Type Description
tuple[float, float] | None

The number of days and percentage worth of effort left, or None if there is

tuple[float, float] | None

no funding information.

effort_per_day property ¤

Calculate the estimated effort per day.

Considers only working (business) days.

Returns:

Type Description
float | None

Float representing the estimated effort per day over project lifespan.

percent_effort_left property ¤

Provide the percentage of effort left.

Returns:

Type Description
float | None

The percentage of effort left, or None if there is no funding information.

total_effort property ¤

Provide the total days worth of effort available from funding.

Returns:

Type Description
int | None

The total number of days effort, or None if there is no funding information.

total_working_days property ¤

Provide the total number of working (business) days given the dates.

Returns:

Type Description
int | None

Number of working days between the project start and end date.

weeks_to_deadline property ¤

Provide the number of weeks left until project deadline.

Only relevant for active projects.

Returns:

Type Description
tuple[int, float] | None

The number of weeks left or None if the project is not Active.

Functions¤
__str__() ¤

String representation of the Project object.

Source code in main/models.py
186
187
188
def __str__(self) -> str:
    """String representation of the Project object."""
    return self.name
check_and_notify_status() ¤

Check the project status and notify accordingly.

Source code in main/models.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def check_and_notify_status(self) -> None:
    """Check the project status and notify accordingly."""
    from .tasks import notify_left_threshold

    check = False

    assert self.lead and hasattr(self.lead, "email")

    for threshold in sorted(EFFORT_LEFT_THRESHOLD):
        if self.percent_effort_left is None or self.percent_effort_left > threshold:
            continue

        if str(threshold) in self.notifications_effort:
            # Already notified for this threshold in the past
            break

        notify_left_threshold(
            email=self.lead.email,
            lead=self.lead.get_full_name(),
            project_name=self.name,
            threshold_type="effort",
            threshold=threshold,
            value=self.days_left[0] if self.days_left else 0,
        )
        self.notifications_effort[str(threshold)] = (
            datetime.today().date().isoformat()
        )
        check = True
        break

    for threshold in sorted(WEEKS_LEFT_THRESHOLD):
        if self.weeks_to_deadline is None or self.weeks_to_deadline[1] > threshold:
            continue

        if str(threshold) in self.notifications_weeks:
            # Already notified for this threshold in the past
            break

        notify_left_threshold(
            email=self.lead.email,
            lead=self.lead.get_full_name(),
            project_name=self.name,
            threshold_type="weeks",
            threshold=threshold,
            value=self.weeks_to_deadline[0] if self.weeks_to_deadline else 0,
        )
        self.notifications_weeks[str(threshold)] = (
            datetime.today().date().isoformat()
        )
        check = True
        break

    if check:
        self.save(update_fields=["notifications_effort", "notifications_weeks"])
clean() ¤

Ensure that all fields have a value unless the status is 'Draft'.

It also checks that, if present, the end date is after the start date.

Source code in main/models.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def clean(self) -> None:
    """Ensure that all fields have a value unless the status is 'Draft'.

    It also checks that, if present, the end date is after the start date.
    """
    if self.status == "Draft":
        return super().clean()

    if not self.start_date or not self.end_date or not self.lead:
        raise ValidationError(
            "All fields are mandatory except if Project status id 'Draft'."
        )

    if self.end_date <= self.start_date:
        raise ValidationError("The end date must be after the start date.")

TimeEntry ¤

Bases: Model

Time entry for a user.

Functions¤
__str__() ¤

String representation of the Time Entry object.

Source code in main/models.py
694
695
696
def __str__(self) -> str:
    """String representation of the Time Entry object."""
    return f"{self.user} - {self.project} - {self.start_time} to {self.end_time}"

User ¤

Bases: AbstractUser

Custom user model.