Source code for greenlang.calculations.ghg.base.forms
""" GHG base forms."""
import json
from django.utils import timezone
from django import forms
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Hidden, Row, Column, Field
from crispy_forms.bootstrap import AppendedText
from fingreen_web.models import (
    CollectionItem,
    GhgEmissionFactor,
    GhgEmissionFactorValue,
    GhgEmissionSourceComputationMethod,
)
class TaggedFormMixin:
    """ TaggedFormMixin """
    def __init__(self, *args, **kwargs):
        self.user = kwargs.get('user')
        kwargs.pop('user')
        super().__init__(*args, **kwargs)
    def clean_tags(self):
        """ Clean tags field """
        if "tags" in self.data:
            try:
                return json.loads(self.data['tags'])
            except json.JSONDecodeError:
                pass
        return []
    def save_tags(self, instance):
        """ Save tags """
        instance.tags.clear()
        instance.tags.add(*[tag['value'] for tag in self.cleaned_data['tags']], tag_kwargs={
            'creator': self.user,
            'organization': self.user.org_active
        })
[docs]
class PredefinedFactorCalculationMethodForm(TaggedFormMixin, forms.ModelForm):
    """
    This form provides the inputs for category using a predefined factor.
    It exposes the following fields:
    - description_user: a description of the item
    - ghg_factor: the GHG emission factor to use, prefilled with the factors related to selected calculation method
    - value_float: the amount value
    - ghg_unit: the GHG emission unit to use, prefilled with the units related to selected GHG emission factor
    As hidden fields, the form exposes:
    - method: the selected GHG emission source computation method
    - collection: the selected collection
    - item_type: the selected item type, must be 'ghg'
    - ghg_scope: the selected GHG scope
    """  # pylint: disable=line-too-long
    method = forms.ModelChoiceField(
        queryset=GhgEmissionSourceComputationMethod.objects.all(), required=True
    )
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name in self.fields.keys():
            if field_name != 'tags':
                self.fields[field_name].required = True
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_show_labels = False
        method = self.initial["method"]
        self.has_instance = 'instance' in kwargs and kwargs['instance']
        if self.has_instance:
            instance = kwargs['instance']
            if instance.widget_data:
                for key, value in instance.widget_data.items():
                    setattr(self.fields[key], 'initial', value)
        
        factor_ids = (
            GhgEmissionFactorValue.objects.filter(factor__method=method)
            .filter(tot_co2_kg__isnull=False)
            .filter(tot_co2_kg__gt=0)
            .values_list('factor', flat=True)
            .distinct()
        )
        factors = method.factors.filter(id__in=factor_ids).order_by(
            "factor_subtype", "name"
        )
        self.fields["ghg_factor"].choices = [
            (
                factor.pk,
                f"{factor.factor_subtype_repr}{' > ' if factor.factor_subtype else ''}{factor.name}",
            )  # pylint: disable=line-too-long
            for factor in factors
        ]
        if not self.has_instance and len(factors) > 0:
            self.fields["ghg_factor"].initial = factors[0].id
        self.helper.layout = Layout(
            Hidden(
                "method", self.initial["method"].id
            ),  # field used in the view to preset form
            Hidden("collection", self.initial["collection"].id),
            Hidden("item_type", self.initial["item_type"]),
            Hidden("ghg_scope", self.initial["ghg_scope"]),
            Row(
                Column(
                    Field(
                        "description_user",
                        placeholder=_("Description"),
                        type="text",
                        autocomplete="off",
                        css_class="form-control form-control-lg form-control-solid mb-3 mb-lg-0",
                    ),
                    css_class="col-md-6",
                ),
                Column(
                    Field(
                        "tags",
                        css_class="form-control tags-input",
                    ),
                    css_class="col-md-6",
                ),
            ),
            Row(
                Column(
                    Field(
                        "ghg_factor",
                        data_control="select2",
                        data_placeholder=_("Choose a GHG emission factor"),
                        hx_post=reverse("ghg_factor_units"),
                        hx_swap="innerHTML",
                        hx_target="#custom_units",
                        hx_trigger="load, change",
                        hx_include="[name='ghg_factor']",
                        style="display: none;",
                        css_class="form-control",
                    ),
                    css_class="col-5",
                ),
                Column(
                    Field(
                        "value_float",
                        placeholder=_("Enter amount"),
                        min=0,
                        type="number",
                        autocomplete="off",
                        css_class="form-control form-control-lg form-control-solid mb-3 mb-lg-0",
                    ),
                    css_class="col-2",
                ),
                Column(
                    Field(
                        "ghg_unit",
                        data_placeholder=_("Select an emission source 1st"),
                        data_control="select2",
                        style="display: none;",
                        css_class="form-control",
                    ),
                    id="custom_units",
                    css_class="col",
                ),
                *self.get_extra_fields(),
                Column(
                    Submit(
                        "submit",
                        _("Add") if not self.has_instance else _("Update"),
                        css_class="btn btn-light-primary",
                    ),
                    css_class="col",
                ),
            ),
        )
[docs]
    def get_extra_fields(self):
        """
        Return extra fields to add to the form.
        This method is meant to be overriden by subclasses.
        Return:
            A list of extra fields to add to the form. Empty list by default.
        """
        return []
[docs]
    def save(self, commit=True):
        """Save"""
        instance = super().save(commit=False)
        if commit:
            if self.user:
                instance.value_last_editor = self.user
            instance.value_update_date = timezone.now()
            
            instance.save()
            self.save_tags(instance)
        return instance
    class Meta:
        model = CollectionItem
        fields = [
            "collection",
            "ghg_scope",
            "item_type",
            "description_user",
            "tags",
            "ghg_factor",
            "value_float",
            "ghg_unit",
        ]
[docs]
class CustomFactorCalculationMethodForm(TaggedFormMixin, forms.ModelForm):
    """CustomFactorCalculationMethodForm"""
    method = forms.ModelChoiceField(
        queryset=GhgEmissionSourceComputationMethod.objects.all(), required=True
    )
    custom_factor_name = forms.CharField(required=True)
    custom_factor_value = forms.FloatField(required=True)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name in self.fields.keys():
            if field_name != 'tags':
                self.fields[field_name].required = True
        
        has_instance = 'instance' in kwargs and kwargs['instance']
        if has_instance:
            instance = kwargs['instance']
            if instance.widget_data:
                for key, value in instance.widget_data.items():
                    setattr(self.fields[key], 'initial', value)
        self.factor_type = "custom"
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_show_labels = False
        self.fields["custom_factor_value"].widget.attrs["placeholder"] = _(
            "Emission factor value"
        )
        self.fields["custom_factor_name"].widget.attrs["placeholder"] = _(
            _("Emission factor name")
        )
        self.fields["custom_factor_value"].widget.attrs["min"] = 0
        self.fields["custom_factor_value"].widget.attrs["step"] = 0.01
        self.helper.layout = Layout(
            Hidden(
                "method", self.initial["method"].id
            ),  # field used in the view to preset form
            Hidden("collection", self.initial["collection"].id),
            Hidden("item_type", self.initial["item_type"]),
            Hidden("ghg_scope", self.initial["ghg_scope"]),
            Row(
                Column(
                    Field(
                        "description_user",
                        placeholder=_("Description"),
                        type="text",
                        autocomplete="off",
                        css_class="form-control form-control-lg form-control-solid mb-3 mb-lg-0",
                    ),
                    css_class="col-md-6",
                ),
                Column(
                    Field(
                        "tags",
                        css_class="form-control tags-input",
                    ),
                    css_class="col-md-6",
                ),
            ),
            Row(
                Column(
                    Field(
                        "custom_factor_name",
                        # do not use placeholder here, it could be more difficult to override
                        type="text",
                        autocomplete="off",
                        css_class="form-control form-control-lg form-control-solid mb-3 mb-lg-0",
                    ),
                    css_class="col-3",
                ),
                *self.get_value_float_columns(),
                Column(
                    AppendedText("custom_factor_value", "kg CO2e/ unit"),
                    css_class="col-3",
                ),
                *self.get_extra_fields(),
                Column(
                    Submit(
                        "submit",
                        _("Add") if not has_instance else _("Update"),
                        css_class="btn btn-light-primary w-100",
                    ),
                    css_class="col-1",
                ),
            ),
        )
[docs]
    def get_value_float_columns(self):
        """ Get value float columns """
        return [
            Column(
                Field(
                    "value_float",
                    placeholder=self.get_placeholder('value_float'),
                    min=0,
                    type="number",
                    autocomplete="off",
                    css_class="form-control form-control-lg form-control-solid mb-3 mb-lg-0",
                ),
                css_class="col-2",
            ),
            Column(
                Field(
                    "ghg_unit",
                    data_placeholder=_("Select an emission source 1st"),
                    data_control="select2",
                    style="display: none;",
                    css_class="form-control",
                ),
                id="custom_units",
                css_class="col-2",
            )
        ]
[docs]
    def get_extra_fields(self):
        """
        Return extra fields to add to the form.
        This method is meant to be overriden by subclasses.
        Return:
            A list of extra fields to add to the form. Empty list by default.
        """
        return []
[docs]
    def get_placeholder(self, field_name):
        """ Get placeholder """
        if field_name == "value_float":
            return _("Amount")
        return ""
[docs]
    def save(self, commit=True):
        """Save"""
        instance = super().save(commit=False)
        custom_factor_name = self.cleaned_data["custom_factor_name"]
        custom_factor_value = self.cleaned_data["custom_factor_value"]
        method = self.cleaned_data["method"]
        instance.widget_data = {
            "custom_factor_name": custom_factor_name,
            "custom_factor_value": custom_factor_value,
        }
        if instance.item_type == "ghg":
            instance.ghg_factor = GhgEmissionFactor.objects.create(
                name=custom_factor_name,
                factor_type=self.factor_type,
                method=method,
                organization=self.user.org_active,
            )
            GhgEmissionFactorValue.objects.update_or_create(
                factor=instance.ghg_factor,
                unit=instance.ghg_unit,
                defaults={
                    "tot_co2_kg": custom_factor_value,
                }
            )
        if commit:
            if self.user:
                instance.last_editor = self.user
            instance.value_update_date = timezone.now()
            
            instance.save()
            self.save_tags(instance)
        return instance
    class Meta:
        model = CollectionItem
        fields = [
            "collection",
            "ghg_scope",
            "item_type",
            "description_user",
            "tags",
            "value_float",
            "ghg_unit",
        ]
[docs]
class SupplierSpecificMethodForm(CustomFactorCalculationMethodForm):
    """Supplier specific method"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["custom_factor_name"].label = _("Emission Source Name")
        self.fields["custom_factor_name"].widget.attrs["placeholder"] = _("Emission Source Name")
        self.factor_type = "supplier"
    class Meta(CustomFactorCalculationMethodForm.Meta):
        pass
[docs]
class CustomAverageDataMethodForm(CustomFactorCalculationMethodForm):
    """Custom Average data method form"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["custom_factor_name"].label = _("Name")
        self.fields["custom_factor_name"].widget.attrs["placeholder"] = _("Name")
        self.factor_type = "goods"
    class Meta(CustomFactorCalculationMethodForm.Meta):
        pass