Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 220 additions & 82 deletions app/projects/forms.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,32 @@
import json
import logging
import pickle
import os
import json
import io
import csv
from django.db.models import Q
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from openpyxl import load_workbook
import numpy as np
import pickle

from crispy_forms.bootstrap import AppendedText, PrependedText, FormActions
import numpy as np
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Submit,
Layout,
Row,
Column,
Field,
Fieldset,
ButtonHolder,
)
from crispy_forms.layout import Submit
from dashboard.helpers import KPI_PARAMETERS_ASSETS
from django import forms
from django.forms import ModelForm
from django.conf import settings as django_settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import Q
from django.forms import ModelForm
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.conf import settings as django_settings
from projects.models import *
from projects.constants import MAP_EPA_MVS, RENEWABLE_ASSETS, CURRENCY_SYMBOLS

from dashboard.helpers import KPI_PARAMETERS_ASSETS, KPIFinder
from projects.helpers import (
parameters_helper,
PARAMETERS,
DualNumberField,
parse_input_timeseries,
TimeseriesField,
TS_SELECT_TYPE,
TS_UPLOAD_TYPE,
TS_MANUAL_TYPE,
)

from projects.constants import ASSET_TO_TIMESERIES_ASSET_TYPE
from projects.constants import CURRENCY_SYMBOLS
from projects.constants import RENEWABLE_ASSETS
from projects.helpers import PARAMETERS
from projects.helpers import TS_MANUAL_TYPE
from projects.helpers import TS_SELECT_TYPE
from projects.helpers import TS_UPLOAD_TYPE
from projects.helpers import DualNumberField
from projects.helpers import TimeseriesField
from projects.helpers import parameters_helper
from projects.models import *


def gettext_variables(some_string, lang="de"):
Expand Down Expand Up @@ -109,9 +94,7 @@ def set_parameter_info(param_name, field, parameters=PARAMETERS):
unit = PARAMETERS[param_name][":Unit:"]
verbose = PARAMETERS[param_name]["verbose"]
default_value = PARAMETERS[param_name][":Default:"]
if unit == "None" or unit == "":
unit = None
elif unit == "Factor":
if unit == "None" or unit == "" or unit == "Factor":
unit = None
if verbose == "None":
verbose = None
Expand All @@ -138,7 +121,7 @@ class OpenPlanModelForm(ModelForm):
"""Class to automatize the assignation and translation of the labels, help_text and units"""

def __init__(self, *args, **kwargs):
super(OpenPlanModelForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
for fieldname, field in self.fields.items():
set_parameter_info(fieldname, field)

Expand All @@ -151,7 +134,7 @@ class OpenPlanForm(forms.Form):
"""Class to automatize the assignation and translation of the labels, help_text and units"""

def __init__(self, *args, **kwargs):
super(OpenPlanForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
for fieldname, field in self.fields.items():
set_parameter_info(fieldname, field)

Expand All @@ -168,7 +151,7 @@ class Meta:
exclude = ["date_created", "date_updated", "economic_data", "user", "viewers"]

def __init__(self, *args, **kwargs):
super(ProjectDetailForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.disabled = True

Expand All @@ -179,7 +162,7 @@ class Meta:
fields = "__all__"

def __init__(self, *args, **kwargs):
super(EconomicDataDetailForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.disabled = True

Expand Down Expand Up @@ -331,7 +314,7 @@ class ProjectCreateForm(OpenPlanForm):

# Render form
def __init__(self, *args, **kwargs):
super(ProjectCreateForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = "project_form_id"
# self.helper.form_class = 'blueForm'
Expand Down Expand Up @@ -712,7 +695,7 @@ def __init__(self, *args, **kwargs):
proj_id = kwargs.pop("proj_id", None)
scenario_id = kwargs.pop("scenario_id", None)
view_only = kwargs.pop("view_only", False)
self.existing_asset = kwargs.get("instance", None)
self.existing_asset = kwargs.get("instance")
# get the connections with busses
self.input_output_mapping = kwargs.pop("input_output_mapping", None)

Expand Down Expand Up @@ -882,37 +865,6 @@ def is_input_timeseries_empty(self):
else:
return True

def clean_input_timeseries_old(self):
"""Override built-in Form method which is called upon form validation"""
try:
input_timeseries_values = []
timeseries_file = self.files.get("input_timeseries_file", None)
# read the timeseries from file if any
if timeseries_file is not None:
input_timeseries_values = parse_input_timeseries(timeseries_file)
# TODO here list the possible options
else:
# set the previous timeseries from the asset if any
if self.is_input_timeseries_empty() is False:
input_timeseries_values = (
self.existing_asset.input_timeseries_values
)
return input_timeseries_values
except json.decoder.JSONDecodeError as ex:
raise ValidationError(
_(
"File not properly formatted. Please ensure you upload a comma separated array of values. E.g. [1,2,0.32]"
)
)
except TypeError as e:
raise ValidationError(str(e))
except Exception as ex:
raise ValidationError(
_(
f"Could not parse a file due to the following error: {ex}. Did you upload a file?"
)
)

def clean_efficiency_multiple(self):
data = self.cleaned_data["efficiency_multiple"]
if self.asset_type_name == "chp_fixed_ratio":
Expand Down Expand Up @@ -1078,11 +1030,6 @@ class Meta:
"lifetime": forms.NumberInput(
attrs={"placeholder": "e.g. 10 years", "min": "0", "step": "1"}
),
"input_timeseries_old": forms.FileInput(
attrs={
"onchange": "plot_file_trace(obj=this.files, plot_id='timeseries_trace')"
}
),
"crate": forms.NumberInput(
attrs={
"placeholder": "factor of total capacity (kWh), e.g. 0.7",
Expand Down Expand Up @@ -1159,7 +1106,7 @@ class Meta:
class StorageForm(AssetCreateForm):
def __init__(self, *args, **kwargs):
asset_type_name = kwargs.pop("asset_type", None)
super(StorageForm, self).__init__(*args, asset_type="capacity", **kwargs)
super().__init__(*args, asset_type="capacity", **kwargs)
self.fields["dispatchable"].widget = forms.HiddenInput()
self.initial["dispatchable"] = True

Expand Down Expand Up @@ -1226,3 +1173,194 @@ class Meta:
},
)
}


class CreatePVProductionTimeseriesForm(OpenPlanForm):
mounting_type_choices = (
("fix_tilt", _("Fix Tilt")),
("fix_tilt_two_dir", _("Fix Tilt Two Directions Back To Back")),
("tracker", _("Tracker")),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# TODO: these parameters would not be manual inputs but come from weather data, I assume? check with Markus

# direct_irradiation_horizontal =
# diffuse_irradiation_horizontal =
azimuth = forms.FloatField(
label=_("Azimuth"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 180"),
"data-bs-toggle": "tooltip",
"title": _(
"For fix tilt: Azimuth angle of the module orientation in degrees (North is 0°, East is 90°...); For tracker: Azimuth angle of the rotation-axis for tracking systems"
),
}
),
)

tilt = forms.FloatField(
label=_("Tilt"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 180"),
"data-bs-toggle": "tooltip",
"title": _("Tilt angle in degrees (0° is horizontal, 90° is vertical)"),
}
),
)

system_efficiency = forms.FloatField(
label=_("System Efficiency"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 0.8"),
"data-bs-toggle": "tooltip",
"title": _(
"Performance ratio of the total PV-System (usually around 0.8)"
),
}
),
)

gcr = forms.FloatField(
label=_("Ground Coverage Ratio"),
widget=forms.NumberInput(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"Ground Coverage Ratio (Ratio of the module area to the ground area of the module field), only needed for tracker"
),
}
),
required=False,
)

mounting_type = forms.ChoiceField(
choices=mounting_type_choices,
label=_("Mounting Type"),
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"Static systems, east-west like system or 1-axis tracking system"
),
}
),
)
albedo = forms.FloatField(
label=_("Albedo"),
widget=forms.NumberInput(
attrs={
"data-bs-toggle": "tooltip",
"title": _("Reflection fraction of sunlight in the surrounding area"),
}
),
)

# TODO: Add validation that checks e.g. that this field is only filled in if tracker is selected
max_angle = forms.FloatField(
label=_("Max. tilt angle"),
widget=forms.NumberInput(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"Maximum tilt angle for tracking systems. This value is only used for 'tracker' systems"
),
}
),
required=False,
)


class CreateHeatDemandForm(OpenPlanForm):
profile_type_choices = (
("EFH", "Single-family house"),
("MFH", "Apartment building"),
("GHD", "Commerce/Services general"),
("GMF", "Household-like business enterprises"),
("GGA", "Restaurants"),
("GBH", "Retail and wholesale"),
("GMK", "Metal and automotive"),
("GBH", "Accommodation"),
("GKO", "Local authorities, credit institutions and insurance companies"),
("GBD", "Other operational services"),
("GWA", "Laundries, dry cleaning"),
("GGB", "Horticulture"),
("GBA", "Bakery"),
("GPD", "Paper and printing"),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# TODO: is this meant to be a DualNumberField?
outdoor_temperature = forms.FloatField(
label=_("Outdoor Temperature"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 25"),
"data-bs-toggle": "tooltip",
"title": _("Outside air temperature in °C"),
}
),
)

profile_type = forms.ChoiceField(
choices=profile_type_choices,
label=_("Profile Type"),
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _("Select from one of the available BDEW heat profiles"),
}
),
)

annual_heat_demand = forms.FloatField(
label=_("Annual Heat Demand"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 1000"),
"data-bs-toggle": "tooltip",
"title": _("Total heat demand in the chosen timeperiod"),
}
),
)

# TODO: here also check the validation of when the field is required
building_year = forms.FloatField(
label=_("Building Year"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 1970"),
"data-bs-toggle": "tooltip",
"title": _(
"Only for residential buildings, used for estimating insulation"
),
}
),
required=False,
)

wind_class = forms.ChoiceField(
label=_("Wind class"),
choices=(("windy", _("Windy")), ("not_windy", _("Not Windy"))),
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"Windy for exposed buildings on free fields, near coast or high ground. Not windy for unexposed buildings in villages/cities"
),
}
),
)


CUSTOM_TIMESERIES_FORMS = {
"pv_plant": CreatePVProductionTimeseriesForm,
"heat_demand": CreateHeatDemandForm,
}
Loading
Loading