Add extra logic to your Django forms!
pip install django_alo_forms
in forms.py
from alo import forms
from alo.operators import AND, OR
class BookForm(forms.QueryForm):
# Django form fields
year = forms.IntegerField(required=False, min_value=1970, max_value=2012)
title = forms.CharField(required=False)
genre = forms.CharField(required=False)
author = forms.CharField(required=False)
house = forms.CharField(required=False)
class Meta:
lookups = {
# field_name: model_field__lookups
'year': 'publication_date__year',
'title': 'title__icontains',
'genre': 'genres',
'author': 'author_id',
'house': 'publishing_house__id',
}
extralogic = [
# Combine the form fields with boolean logic
AND('genre', OR('author', 'house')),
# if 'genre' provided, so should also be either 'author' or 'house'
OR('title', 'year', required=True)
# either 'year' or 'title' must be provided
]
class BookModelForm(forms.QueryModelForm):
class Meta:
model = Book
fields = (
'publication_date', 'title', 'genres',
'author', 'publishing_house'
)
lookups = {
'publication_date': 'publication_date__year',
'title': 'title__icontains',
}
extralogic = [
AND('genres', 'author', 'year')),
]
in views.py
from .forms import BookForm
from .models import Book
def example(request):
form = BookForm(request.GET)
if form.is_valid():
# form.parameters is like form.cleaned_data but
# with lookups applied and without empty values
Book.objects.filter(**form.parameters)
...
...
Instead of the example view above, you can use the validate
decorator as follows:
from alo.decorators import validate
from .forms import BookForm
from .models import Book
@validate(BookForm)
def example(request):
# enters view if form is valid
Book.objects.filter(**request.form.parameters)
...
If form
is not valid, validate
returns a JsonResponse with form.errors
as content and status code 400
.
It is worth noting that validate
works in any kind of views (function-base, class-bases, custom-bases) since it scans the view arguments for the HttpRequest
object to validate the form.
Besides, validate
is able to detect if the given form class is a subclass Form
or ModelForm
. In case of the latter, the decorator instantiates the form with the instance
argument if the named group pk
is present in the urlpattern (useful for getting or updating a resource). You can change the expected named group using the decorator argument add_instance_using
.
Group multiple fields to a single lookup. Useful for ranges and geo-lookups.
from django.contrib.gis.measure import D
from alo import forms
class StoreForm(forms.QueryForm):
books = forms.IntegerField(required=False)
range = forms.IntegerField(required=False)
center = forms.PointField(required=False)
radius = forms.IntegerField(required=False)
class Meta:
multifield_lookups = {
# tuple_of_field_names: callable_that_returns_a_dict
('center', 'radius'): lambda center,radius: {
'location__distance_lte': (center, D(km=radius))
},
('books', 'range'): lambda books,range: {
'pages__range': (books-range, books+range)
},
}
extralogic = [
AND('books', 'ranges'),
AND('center', 'radius'),
]
By default, QueryForm.parameter
and QueryModelForm.parameter
instance attribute use the initial
field's argument as the default value when no input is given for that particular field.
Beware the
BooleanField
in Django will automatically be set toFalse
if no input given, regardless of theinitial
value.
from alo import forms
class BookForm(forms.QueryForm):
pages = forms.IntegerField(required=False)
range = forms.IntegerField(required=False, initial=50)
class Meta:
extralogic = [
AND('pages', 'range')
# Even though `range` has a default value it will only be taken
# into account if `pages` is provided. In other words the form is
# valid wether `pages` is provided or not
]
To disable this feature, set no_defaults
meta option to True
.
Exclude a field from the form's parameters
attribute. The field is still cleaned, validated and is accessible in the cleaned_data
attribute.
from alo import forms
class BookForm(forms.QueryForm):
foo = forms.IntegerField(required=False)
bar = forms.IntegerField(required=False, initial=50)
class Meta:
ignore = ['foo']
Indicate which fields are required. You may use this to override auto generated fields in QueryModelForm
.
from alo import forms
class StoreForm(forms.QueryModelForm):
class Meta:
model = Store
required = ['name', 'address']
It can also be used as an altenative to explicitly pass the required
argument to each field (if required = []
no field is required).
By default required
is None
, which means each field will assume its original property.
Besides the fields that django already provides, there are three more fields that you may find useful: CoordsField
, BoundingBoxField
and CircleField
. All require the GEOS library to be available.
from alo import forms
class CityForm(forms.QueryForm):
coords = forms.CoordsField()
class Meta:
lookups = {
'coords': 'area__contains'
}
- inherits from
CharField
- similar to
PointField
but with a friendlier input format - expected input format:
'<float>,<float>'
- converts the input to a
Point
- takes an additional argument:
latitude_first
(default toFalse
). If set toTrue
, it is expected that the first givenfloat
to be the latitude and the second the longitude.
from alo import forms
class StoreForm(forms.QueryForm):
box = forms.BoundingBoxField()
class Meta:
lookups = {
'box': 'point__contained'
}
- inherits directly from
MultiValueField
- expected inputs:
<fieldname>_0
and<fieldname>_1
- converts the input to a
Polygon
- takes an additional argument:
latitude_first
(defaults toFalse
)
from alo import forms
class StoreForm(forms.QueryForm):
center = forms.CircleField()
class Meta:
lookups = {
'center': 'point__distance_lte'
}
- inherits directly from
CoordsField
- expected the same inputs as its parent class
- converts the input to a tuple:
(<Point>, <Distance object>)
- takes two additional argument:
distance
(defaults to5
) andunit
(defaults to'km'
)