Source code for lback.forms.widgets

from typing import Any, Dict, Optional, List, Tuple

[docs] class Widget: """ Base class for all form widgets. Widgets are responsible for rendering the HTML representation of a field. """ input_type = None template_name = None
[docs] def __init__(self, attrs: Optional[Dict[str, Any]] = None): """ Initializes the widget with optional HTML attributes. Args: attrs: A dictionary of HTML attributes (e.g., {'class': 'my-input', 'placeholder': 'Enter text'}). """ self.attrs = attrs if attrs is not None else {}
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the widget as an HTML string. This method should be overridden by subclasses. Args: name: The HTML 'name' attribute for the input element. value: The current value of the field. attrs: Additional HTML attributes to include (merged with self.attrs). Returns: An HTML string representing the widget. """ raise NotImplementedError("Subclasses must implement the 'render' method.")
[docs] def build_attrs(self, base_attrs: Optional[Dict[str, Any]] = None, extra_attrs: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Helper method to combine base attributes, widget attributes, and extra attributes. Args: base_attrs: Attributes from the field itself. extra_attrs: Attributes passed during rendering (e.g., in template tags). Returns: A combined dictionary of attributes. """ attrs = {} if base_attrs: attrs.update(base_attrs) attrs.update(self.attrs) if extra_attrs: attrs.update(extra_attrs) return attrs
[docs] def value_from_datadict(self, data: Dict[str, Any], files: Dict[str, Any], name: str) -> Any: """ Given a dictionary of data and an attribute name, returns the value of that attribute, normalizing it if necessary. Args: data: The dictionary of data (e.g., request.POST). files: The dictionary of files (e.g., request.FILES). name: The name of the field. Returns: The raw value from the data dictionary. """ return data.get(name)
[docs] class TextInput(Widget): """ A widget that renders as a standard HTML <input type="text">. """ input_type = 'text'
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the text input widget. """ if value is None: value = '' value_str = str(value) final_attrs = self.build_attrs(attrs, {'type': self.input_type, 'name': name, 'value': value_str}) attrs_string = " ".join([f'{key}="{value}"' for key, value in final_attrs.items()]) return f'<input {attrs_string}>'
[docs] class Textarea(Widget): """ A widget that renders as an HTML <textarea> element. """
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the textarea widget. """ if value is None: value = '' value_str = str(value) final_attrs = self.build_attrs(attrs, {'name': name}) attrs_string = " ".join([f'{key}="{value}"' for key, value in final_attrs.items()]) return f'<textarea {attrs_string}>{value_str}</textarea>'
[docs] def value_from_datadict(self, data: Dict[str, Any], files: Dict[str, Any], name: str) -> Any: """ Textarea value is also just retrieved from data dictionary. """ return data.get(name)
[docs] class CheckboxInput(Widget): """ A widget that renders as an HTML <input type="checkbox">. Handles boolean values. """ input_type = 'checkbox'
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the checkbox input widget. Handles setting the 'checked' attribute based on the value. """ final_attrs = self.build_attrs(attrs, {'type': self.input_type, 'name': name}) if value: final_attrs['checked'] = '' if 'value' not in final_attrs: final_attrs['value'] = 'on' attrs_string = " ".join([f'{key}="{value}"' for key, value in final_attrs.items()]) return f'<input {attrs_string}>'
[docs] def value_from_datadict(self, data: Dict[str, Any], files: Dict[str, Any], name: str) -> Any: """ For checkboxes, the value is only present in data if checked. We need to return a boolean based on whether the name exists in data. """ return name in data
[docs] class Select(Widget): """ A widget that renders as an HTML <select> element. Takes a list of choices. """
[docs] def __init__(self, choices: List[Tuple[Any, str]], attrs: Optional[Dict[str, Any]] = None): """ Initializes the Select widget. Args: choices: A list of (value, label) tuples for the select options. attrs: Optional HTML attributes for the <select> element. """ super().__init__(attrs=attrs) self.choices = choices
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the select widget with options. Handles selecting the correct option based on the current value. """ final_attrs = self.build_attrs(attrs, {'name': name}) attrs_string = " ".join([f'{key}="{value}"' for key, value in final_attrs.items()]) options_html = [] for option_value, option_label in self.choices: selected_attr = ' selected' if str(value) == str(option_value) else '' options_html.append(f'<option value="{option_value}"{selected_attr}>{option_label}</option>') return f'<select {attrs_string}>\n{"".join(options_html)}\n</select>'
[docs] def value_from_datadict(self, data: Dict[str, Any], files: Dict[str, Any], name: str) -> Any: """ Select value is retrieved from data dictionary. """ return data.get(name)
[docs] class PasswordInput(TextInput): """ A widget that displays a password input (<input type="password">). By default, it does not pre-fill the value for security reasons. """ input_type = 'password'
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the password input. For security, the value attribute is usually left empty. """ final_attrs = self.build_attrs(attrs, {'type': self.input_type, 'name': name}) if 'value' in final_attrs: del final_attrs['value'] attr_string = ' '.join(f'{k}="{v}"' for k, v in final_attrs.items() if v is not None) return f'<input {attr_string}>'
[docs] class FileInput(Widget): """ A widget that renders as an HTML <input type="file"> element. Handles file uploads. """ input_type = 'file'
[docs] def render(self, name: str, value: Any, attrs: Optional[Dict[str, Any]] = None) -> str: """ Renders the file input widget. Note: The 'value' attribute is typically NOT set for security reasons in file input fields. The browser handles displaying the selected file name. """ final_attrs = self.build_attrs(attrs, {'type': self.input_type, 'name': name}) attrs_string = " ".join([f'{key}="{value}"' for key, value in final_attrs.items()]) return f'<input {attrs_string}>'
[docs] def value_from_datadict(self, data: Dict[str, Any], files: Dict[str, Any], name: str) -> Any: """ For file inputs, the value comes from the 'files' dictionary, not 'data'. """ return files.get(name)