Source code for ferrosoft.apps.emiflow.forms.intensity
# Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from django import forms
from django.forms import ValidationError
from django.utils.translation import gettext_lazy as _
from ferrosoft.apps.emiflow.models import (
EmissionIntensity,
EnergyCarrier,
EmissionFactor,
EmissionCategory,
)
from ferrosoft.apps.emiflow.services.emissions import EmissionIntensityCalculator
from ferrosoft.apps.ferrobase.forms import UnitChoiceField
from ferrosoft.apps.ferrobase.models import MeasurementUnit, UnitCollection, UnitCursor
[docs]
class EmissionIntensityForm(forms.ModelForm):
[docs]
class Meta:
model = EmissionIntensity
fields = [
"enable_distance",
"owner_type",
"owner_id",
"factor_operation_value",
"factor_energy_provision_value",
"quantity_value",
"quantity_unit",
"mass_value",
"mass_unit",
"distance_value",
"distance_unit",
]
widgets = {
"enable_distance": forms.HiddenInput(),
"owner_type": forms.HiddenInput(),
"owner_id": forms.HiddenInput(),
}
quantity_unit = UnitChoiceField(
units=UnitCollection.NATIVE_CONSUMPTION,
initial=UnitCursor("kg"),
)
mass_unit = UnitChoiceField(
units=UnitCollection.MASS,
initial=UnitCursor("t"),
)
distance_unit = UnitChoiceField(
units=UnitCollection.DISTANCE,
initial=UnitCursor("km"),
)
def __init__(self, *args, **kwargs):
self.energy_carrier: EnergyCarrier | None = kwargs.pop("energy_carrier")
enable_distance: bool = kwargs.pop("enable_distance")
super().__init__(*args, **kwargs)
operation_factors = EmissionFactor.objects.filter(
category=EmissionCategory.OPERATION
)
energy_provision_factors = EmissionFactor.objects.filter(
category=EmissionCategory.ENERGY_PROVISION
)
if self.energy_carrier is not None:
# Constrain factors to sensible set based on energy carrier.
operation_factors = operation_factors.filter(
energy_carrier_id=self.energy_carrier.pk
)
energy_provision_factors = energy_provision_factors.filter(
energy_carrier_id=self.energy_carrier.pk
)
# Remove Liter unit if density is not set, as a Liter value
# cannot be converted to Kilogram in this case.
if self.energy_carrier.density is None:
self.fields["quantity_unit"].choices = filter(
lambda choice: choice[0] != "13e6e7ee-e33c-4f82-b824-09e7697aeef3",
self.fields["quantity_unit"].choices,
)
# Distance is hidden on request, which is useful for hub equipment.
if not enable_distance:
self.fields["distance_value"].widget = forms.HiddenInput()
self.fields["distance_unit"].widget = forms.HiddenInput()
self.fields["factor_energy_provision_value"].queryset = energy_provision_factors
self.fields["factor_operation_value"].queryset = operation_factors
[docs]
def clean_quantity_unit(self):
# Volumetric quantities cannot be converted to kilogram with energy
# carriers without density.
value = self.cleaned_data["quantity_unit"]
is_volumetric = value.cursor in UnitCollection.VOLUMETRIC
if (
is_volumetric
and self.energy_carrier is not None
and self.energy_carrier.density is None
):
raise ValidationError(
_(
"Volume unit cannot be used because the energy carrier has no density."
),
code="energy_carrier_no_density",
)
return value
[docs]
def clean(self):
cleaned_data = super().clean()
if (
"factor_operation_value" not in cleaned_data
or "factor_energy_provision_value" not in cleaned_data
):
return
# Units of factors must match each other.
if (
cleaned_data["factor_operation_value"].unit.symbol
!= cleaned_data["factor_energy_provision_value"].unit.symbol
):
err = _("Factor units must be the same")
self.add_error("factor_operation_value", err)
self.add_error("factor_energy_provision_value", err)
[docs]
def save(self, commit=True):
instance: EmissionIntensity = super().save(commit=False)
# Fudge calculated numbers onto instance.
int_energy_provision, int_operation = (
EmissionIntensityCalculator().calculateFor(instance)
)
instance.operation_value = int_operation.value
instance.energy_provision_value = int_energy_provision.value
instance.unit = int_operation.unit
if commit:
instance.save()
instance.update_foreign_key()
return instance