Forms ===== The framework provides a powerful and flexible Forms system designed to handle HTML form rendering, data validation, and processing with ease. It abstracts away common boilerplate, allowing you to focus on your application's logic. 1- Defining Forms ----------------- Forms are defined as Python classes that inherit from ``lback.forms.forms.Form```. You declare form fields as class attributes, and the framework's ``FormMetaclass`` automatically collects the **Example: A Simple Contact Form** Let's look at how you'd define a contact form: .. code-block:: python # myapp/forms.py from lback.forms.fields import CharField, EmailField, IntegerField, BooleanField from lback.forms.widgets import Textarea, CheckboxInput, PasswordInput from lback.forms.forms import Form from lback.forms.validation import ValidationError # For custom validation class ContactForm(Form): """ A simple form for contact inquiries. Demonstrates various field types and custom validation. """ name = CharField( min_length=3, max_length=100, required=True, label="Your Name", help_text="Please enter your full name." ) email = EmailField( required=True, label="Your Email" ) age = IntegerField( min_value=18, max_value=99, required=False, label="Your Age", help_text="Must be between 18 and 99." ) message = CharField( required=True, widget=Textarea(attrs={'rows': 5, 'cols': 40}), # Custom widget with HTML attributes label="Your Message" ) newsletter_signup = BooleanField( required=False, label="Sign up for newsletter?", widget=CheckboxInput # Explicitly setting checkbox widget ) password = CharField( required=False, widget=PasswordInput, # Renders as label="Password (optional)" ) password_confirm = CharField( required=False, widget=PasswordInput, label="Confirm Password" ) # You can add custom validation logic that applies to a single field def clean_name(self, value): """Custom validation for the 'name' field.""" if "admin" in value.lower(): raise ValidationError("Name cannot contain 'admin'.", code='invalid_name') return value # You can add custom validation logic that applies to the entire form (multiple fields) def clean(self): """ Performs form-level validation. This method is called after individual field validations are complete. """ # Always call the super().clean() to ensure base validations and initial clean_data. # This base clean() method already handles password mismatch, for example. super().clean() # Access cleaned data from individual fields name = self.cleaned_data.get('name') email = self.cleaned_data.get('email') # Example of cross-field validation if name and email and name.lower() == email.split('@')[0].lower(): raise ValidationError("Name cannot be the same as the email's local part.", code='name_email_match') return self.cleaned_data 2- Form Lifecycle & Usage -------------------------- Working with forms typically involves these steps: **a. Instantiating a Form** You can instantiate a form in two main ways: * **Unbound Form (GET requests):** Used when initially displaying an empty form or a form pre-filled with initial data. .. code-block:: python # To display an empty form form = ContactForm() # To display a form with initial values (e.g., for editing an existing entry) initial_data = {'name': 'John Doe', 'email': 'john.doe@example.com'} form = ContactForm(initial=initial_data) * **Bound Form (POST requests):** Used when processing submitted data from a user. The ``data`` and ``files`` arguments should come directly from your request object (e.g., ``request.POST``, ``request.FILES``). .. code-block:: python # In your view handling a POST request form = ContactForm(data=request.POST, files=request.FILES) **b. Validating Form Data** (``is_valid()``) After instantiating a bound form, you must call the ``is_valid()`` method to trigger the validation process. This method validates each field individually and then calls the form's ``clean()`` method for form-level validation. .. code-block:: python # In your view form = ContactForm(data=request.POST) if form.is_valid(): # Form data is valid, access cleaned data name = form.cleaned_data['name'] email = form.cleaned_data['email'] # ... process data (e.g., save to database) return redirect('/success-page/') else: # Form data is invalid, render the form again with errors # The template will use form.errors to display feedback return render(request, 'contact.html', {'form': form}) **c. Accessing Cleaned Data** (``cleaned_data``) If ``is_valid()`` returns ``True``, the validated and converted data for each field is available in the ``form.cleaned_data`` property. This dictionary contains the final, processed values ready for use (e.g., saving to a database). .. code-block:: python if form.is_valid(): user_name = form.cleaned_data['name'] # This will be the cleaned string user_age = form.cleaned_data['age'] # This will be an integer, or None if not required # ... **d. Handling Errors** (``errors``, ``non_field_errors``) If ``is_valid()`` returns ``False``, you can access the validation errors through: * ``form.errors``: A dictionary where keys are field names and values are lists of ``ValidationError`` objects for that field.It also contains the ``__all__`` key for form-level errors. * ``form.non_field_errors``: A convenient property that returns a list of errors that are not specific to any single field (i.e., errors from the ``clean()`` method). You typically pass the form object back to your template to display these errors next to the relevant fields. 3- Field Types -------------- The framework provides a variety of built-in field types to handle different kinds of data: * ``CharField``: For single-line text input. * **Options:** ``min_length``, ``max_length``. * ``EmailField``: Specifically for email addresses, includes email format validation. * ``IntegerField``: For whole numbers. * **Options:** ``min_value``, ``max_value``. * ``BooleanField``: For true/false values, typically rendered as checkboxes. * ``ChoiceField``: For selecting one option from a predefined set. * **Options:** choices (a list of tuples, e.g., ``[('M', 'Male'), ('F', 'Female')]``). * ``DateField``: For dates. * ``TimeField``: For times. * ``DateTimeField``: For date and time. * ``FileField``: For file uploads. All fields support common arguments: * ``required``: ``True`` by default. If ``False``, the field can be left empty. * ``label``: The human-readable label for the field in the HTML form. * ``initial``: The initial value to populate the field with when the form is unbound. * ``help_text``: Explanatory text displayed next to the field. * ``widget``: Allows you to specify a custom HTML widget for the field. 4. Widgets ---------- Widgets determine how a form field is rendered as HTML. You can specify a custom widget using the ``widget`` argument when defining a field. * ``TextInput``: Default for ``CharField``, ``EmailField``, ``IntegerField``. * ``Textarea``: For multi-line text input. * ``PasswordInput``: Renders an ```` field. * ``CheckboxInput``: Renders an ```` field. * ``Select``: Renders a ``` for `DateField``. * ``TimeInput``: Renders an ``` for `TimeField``. * ``DateTimeInput``: Renders an ```` for ``DateTimeField``. * ``FileInput``: Renders an ```` for ``FileField``. You can also pass ``attrs`` (attributes) to widgets to customize their HTML properties: .. code-block:: python message = CharField( widget=Textarea(attrs={'rows': 5, 'class': 'my-custom-textarea'}), label="Your Message" ) Then, in your ``contact.html`` template, you can render the form using one of these methods: * ``{{ form.as_p }}``: Renders each field wrapped in ``

`` tags. .. code-block:: html

{{ form.as_p }}
* ``{{ form.as_ul }}``: Renders each field wrapped in ``
  • `` tags, inside a ``