# Copyright (c) 2024 Ferrosoft GmbH. All rights reserved.
from dataclasses import dataclass
from decimal import Decimal
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
TemplateView,
)
from ferrosoft.apps.emiflow.filter import EmitterFilter, EnergyCarrierFilter
from ferrosoft.apps.emiflow.forms.catalog import (
EmitterForm,
EnergyCarrierForm,
EmissionFactorForm,
)
from ferrosoft.apps.emiflow.forms.settings import (
DefaultsSearchForm,
DefaultsEditForm,
BusinessCentralSettingsForm,
)
from ferrosoft.apps.emiflow.models import (
EmitterEpitype,
Settings,
VehicleType,
Emitter,
IntensityCapableEntity,
TreatmentOperation,
TransportOperation,
BusinessCentralSettings,
EnergyCarrier,
EmissionFactor,
)
from ferrosoft.apps.emiflow.tables import (
EmitterTable,
EnergyCarrierTable,
EmissionFactorTable,
)
from ferrosoft.apps.emiflow.views import intensity_create_url
from ferrosoft.apps.ferrobase.auth.roles import Permissions, Verb
from ferrosoft.apps.ferrobase.forms.copy import copy_form_factory, copy_fields
from ferrosoft.apps.ferrobase.views import (
PlatformActivityMixin,
CreateView,
TableView,
UpdateView,
model_crud_routes,
DeleteView,
DependentCreateView,
)
from ferrosoft.apps.ferrobase.views.generic import CopyView
from ferrosoft.decimal import zero
def _careful_int(value):
try:
return int(value)
except Exception:
return 0
[docs]
@dataclass
class SettingsPage:
title: str
subtitle: str
view: str
SETTINGS_PAGES = [
SettingsPage(
title=_("Emitters"),
subtitle=_("Vehicles and hub equipment"),
view="emiflow:emitter_index",
),
SettingsPage(
title=_("Operations"),
subtitle=_("Operations of vehicles and hub equipment"),
view="emiflow:operation_index",
),
SettingsPage(
title=_("Operation Categories"),
subtitle=_("Groups of operations that share similar characteristics"),
view="emiflow:operationcategory_index",
),
SettingsPage(
title=_("Transport Defaults"),
subtitle=_("Default options for transport imports"),
view="emiflow:settings_transport_defaults",
),
]
[docs]
class SettingsIndex(LoginRequiredMixin, PlatformActivityMixin, TemplateView):
template_name = "emiflow/settings/index.html"
activity_title = _("Settings")
[docs]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["pages"] = SETTINGS_PAGES
return context
[docs]
def settings_transport_defaults(request):
instance = None
search_form = DefaultsSearchForm(request.GET)
if search_form.is_valid():
instance = Settings.get_instance().get_transport_chain_defaults(
VehicleType(
_careful_int(search_form.cleaned_data["vehicle_type"])
or VehicleType.ROAD
),
)
if request.method == "POST" and instance is not None:
edit_form = DefaultsEditForm(request.POST, instance=instance)
if edit_form.is_valid():
edit_form.save()
else:
edit_form = DefaultsEditForm(instance=instance)
return render(
request,
"emiflow/settings/transport_defaults.html",
{
"activity_title": _("Transport defaults"),
"activity_app": "emiflow",
"search_form": search_form,
"edit_form": edit_form,
},
)
[docs]
class BusinessCentralSettingsUpdate(UpdateView):
form_class = BusinessCentralSettingsForm
model = BusinessCentralSettings
[docs]
def get_object(self, queryset=None):
return BusinessCentralSettings.get_instance()
[docs]
class EmitterCopyView(CopyView):
[docs]
@staticmethod
def prepare_copy(source: Emitter, target: Emitter, **kwargs):
def copy_emission_intensity():
# Copy emission intensity as-is and let user adjust it in update view.
source_intensity = getattr(source, "emission_intensity", None)
if source_intensity is not None:
target_intensity = source_intensity.copy_with_owner(
IntensityCapableEntity.EMITTER, target.pk
)
target.emission_intensity = target_intensity
target.save(update_fields=["emission_intensity"])
# All user-defined objects go into user catalog.
target.catalog = Settings.get_instance().user_catalog
return [copy_emission_intensity]
form_class = copy_form_factory(
Emitter,
fields=[
"name",
"epitype",
"vehicle_type",
"energy_carrier",
],
preparator=prepare_copy,
)
model = Emitter
[docs]
class EmitterCreateView(CreateView):
form_class = EmitterForm
help_topic = "emitters"
model = Emitter
[docs]
class EmitterTableView(TableView):
filterset_class = EmitterFilter
help_topic = "emitters"
model = Emitter
ordering = "name"
table_class = EmitterTable
[docs]
class EmitterUpdateView(UpdateView):
add_copy_action = True
form_class = EmitterForm
help_topic = "emitters"
model = Emitter
template_name = "emiflow/emitter/update.html"
[docs]
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"intensity_create_url": intensity_create_url(
IntensityCapableEntity.EMITTER,
self.object.pk,
self.object.energy_carrier_id,
EmitterEpitype(self.object.epitype),
),
}
[docs]
class EmitterDeleteView(DeleteView):
help_topic = "emitters"
model = Emitter
emitter_urls = model_crud_routes(
Emitter,
copy_view=EmitterCopyView,
create_view=EmitterCreateView,
read_view=EmitterTableView,
update_view=EmitterUpdateView,
delete_view=EmitterDeleteView,
)
[docs]
class EnergyCarrierTableView(TableView):
filterset_class = EnergyCarrierFilter
model = EnergyCarrier
ordering = "name"
table_class = EnergyCarrierTable
[docs]
class EnergyCarrierCreateView(CreateView):
form_class = EnergyCarrierForm
model = EnergyCarrier
[docs]
class EnergyCarrierCopyView(CopyView):
[docs]
@staticmethod
def prepare_copy(source: EnergyCarrier, target: EnergyCarrier, **kwargs):
reduction = kwargs.pop("cleaned_data", {}).get("reduction", zero)
reduction_factor = Decimal(1) - reduction / Decimal(100)
def copy_emission_factors():
# Copy emission factors as-is and let user adjust them in update view.
copies = []
for source_factor in source.emission_factors.all():
target_factor = EmissionFactor()
copy_fields(
source_factor,
target_factor,
fields=["unit", "category"],
energy_carrier=target,
value=source_factor.value * reduction_factor,
)
copies.append(target_factor)
EmissionFactor.objects.bulk_create(copies)
# All user-defined objects go into user catalog.
target.catalog = Settings.get_instance().user_catalog
return [copy_emission_factors]
form_class = copy_form_factory(
EnergyCarrier,
additional_fields={
"reduction": forms.DecimalField(
max_digits=5,
decimal_places=2,
initial=zero,
label=_("Emission Factor Reduction"),
help_text=_(
"Copied emission factors are reduced according to this percentage."
),
)
},
fields=[
"name",
"density",
"composition",
"application",
"lower_heating",
],
preparator=prepare_copy,
)
model = EnergyCarrier
[docs]
class EnergyCarrierUpdateView(UpdateView):
add_copy_action = True
form_class = EnergyCarrierForm
model = EnergyCarrier
template_name = "emiflow/energy_carrier/update.html"
[docs]
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"emission_factor_table": EmissionFactorTable(
self.object.emission_factors.order_by("category"),
create_url=reverse(
"emiflow:emissionfactor_create",
query={EmissionFactorCreateView.parent_query: str(self.object.pk)},
),
)
}
[docs]
class EnergyCarrierDeleteView(DeleteView):
model = EnergyCarrier
energy_carrier_urls = model_crud_routes(
EnergyCarrier,
create_view=EnergyCarrierCreateView,
read_view=EnergyCarrierTableView,
update_view=EnergyCarrierUpdateView,
delete_view=EnergyCarrierDeleteView,
copy_view=EnergyCarrierCopyView,
)
[docs]
class EmissionFactorCreateView(DependentCreateView):
form_class = EmissionFactorForm
model = EmissionFactor
parent_model = EnergyCarrier
[docs]
def cancel_view(self):
return self.parent_object.get_absolute_url()
[docs]
class EmissionFactorUpdateView(UpdateView):
form_class = EmissionFactorForm
model = EmissionFactor
related_links_default = False
[docs]
def cancel_view(self):
return self.object.energy_carrier.get_absolute_url()
[docs]
def get_success_url(self):
return self.object.energy_carrier.get_absolute_url()
[docs]
class EmissionFactorDeleteView(DeleteView):
model = EmissionFactor
[docs]
def cancel_view(self):
return self.object.energy_carrier.get_absolute_url()
[docs]
def get_success_url(self):
return self.object.energy_carrier.get_absolute_url()
emission_factor_urls = model_crud_routes(
EmissionFactor,
create_view=EmissionFactorCreateView,
delete_view=EmissionFactorDeleteView,
update_view=EmissionFactorUpdateView,
)