""" Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: ksp-servicestelle@sgdnord.rlp.de Created on: 15.08.22 """ from abc import abstractmethod from django import forms from django.utils.translation import gettext_lazy as _ from compensation.models import EcoAccount from konova.models import BaseObject class BaseForm(forms.Form): """ Basic form for that holds attributes needed in all other forms """ template = None action_url = None action_btn_label = _("Save") form_title = None cancel_redirect = None form_caption = None instance = None # The data holding model object request = None form_attrs = {} # Holds additional attributes, that can be used in the template has_required_fields = False # Automatically set. Triggers hint rendering in templates show_cancel_btn = True label_input_ratio = (3, 9) # used for col-sm-xy in the template. Must sum up to 12. Specify on inheriting forms def __init__(self, *args, **kwargs): self.instance = kwargs.pop("instance", None) super().__init__(*args, **kwargs) if self.request is not None: self.user = self.request.user # Check for required fields for _field_name, _field_val in self.fields.items(): if _field_val.required: self.has_required_fields = True break self.check_for_recorded_instance() self.__check_valid_label_input_ratio() @abstractmethod def save(self): # To be implemented in subclasses! pass def __check_valid_label_input_ratio(self): """ Checks whether the configured label-input ratio is valid If not valid an AssertionError will be raised. The valid sum of label-input ratio is defined by bootstrap's column layout system. Returns: """ ratio = self.label_input_ratio[0] + self.label_input_ratio[1] if ratio != 12: raise AssertionError(f"Label-input ratio on form must sum up to 12! It's {self.label_input_ratio}") def disable_form_field(self, field: str): """ Disables a form field for user editing """ self.fields[field].widget.attrs["readonly"] = True self.fields[field].disabled = True self.fields[field].widget.attrs["title"] = _("Not editable") def initialize_form_field(self, field: str, val): """ Initializes a form field with a value """ self.fields[field].initial = val def add_placeholder_for_field(self, field: str, val): """ Adds a placeholder to a field after initialization without the need to redefine the form widget Args: field (str): Field name val (str): Placeholder Returns: """ self.fields[field].widget.attrs["placeholder"] = val def load_initial_data(self, form_data: dict, disabled_fields: list = None): """ Initializes form data from instance Inserts instance data into form and disables form fields Returns: """ if self.instance is None: return for k, v in form_data.items(): self.initialize_form_field(k, v) if disabled_fields: for field in disabled_fields: self.disable_form_field(field) def add_widget_html_class(self, field: str, cls: str): """ Adds a HTML class string to the widget of a field Args: field (str): The field's name cls (str): The new class string Returns: """ set_class = self.fields[field].widget.attrs.get("class", "") if cls in set_class: return else: set_class += " " + cls self.fields[field].widget.attrs["class"] = set_class def remove_widget_html_class(self, field: str, cls: str): """ Removes a HTML class string from the widget of a field Args: field (str): The field's name cls (str): The new class string Returns: """ set_class = self.fields[field].widget.attrs.get("class", "") set_class = set_class.replace(cls, "") self.fields[field].widget.attrs["class"] = set_class def check_for_recorded_instance(self): """ Checks if the instance is recorded and runs some special logic if yes If the instance is recorded, the form shall not display any possibility to edit any data. Instead, the users should get some information about why they can not edit anything. There are situations where the form should be rendered regularly, e.g deduction forms for (recorded) eco accounts. Returns: """ is_none = self.instance is None is_other_data_type = not isinstance(self.instance, BaseObject) if is_none or is_other_data_type: # Do nothing return if self.instance.is_recorded: self.block_form() def block_form(self): """ Overwrites template, providing no actions Returns: """ self.template = "form/recorded_no_edit.html"