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 ``