# Django3EX - Chapter 2
### Django Forms
* Django forms inherit from the `form` class, it describes how works and apprears.
* A form class fields map to the `<input>` of the HTML form.
```python=
class EmailPostForm(forms.Form):
name = forms.CharField(max_length=50)
email = forms.EmailField()
to = forms.EmailField()
comments = forms.CharField(required=False, widget=forms.Textarea)
```
#### Handeling form in views
* Check the request is `POST` and process the data
```python=
if request.method == 'POST':
```
* Create a form instance and populate it with data from the request
```python=
form = EmailPostForm(request.POST)
```
* Validate the form data with `is_valid()` method.
* `is_valid` validates the data with the form fields and returns a boollean response.
```python=
if form.is_valid():
```
* If the data is valid, you retrive the validated data by accessing `forms.cleaned_data`
```python=
cd = form.cleaned_data
```
* `cd` is a `dict`of form fields and their values.
* Sucessfully redirect the user on sucessfull data submission.
```python=
return redirect(post)
# equivalent to: return HttpResponseRedirect(post.get_absolute_url())
```
##### Complete code snippet
```python=
def post_share(request, post_id):
# Retrieve post by id
post = get_object_or_404(Post, id=post_id, status='published')
if request.method == 'POST':
form = EmailPostForm(request.POST)
if form.is_valid():
# Now do something fancy with the validated data
cd = form.cleaned_data
return redirect(post)
else:
# if a GET (or any other method) we'll create a blank form
form = EmailPostForm()
return render(request,
'blog/post/share.html',
{'post': post, 'form': form}
)
```
#### Handeling form in templates
* All you need to do to get your form into a template is to place the form instance into the template context.
* Taking the above example our form is called `form`.
* `{{ form }}` will render its `<label>` and `<input>` elements appropriately.
* There are other output options though for the `<label>/<input>` pairs:
- `{{ form.as_table }}` will render them as table cells wrapped in `<tr>` tags
- `{{ form.as_p }}` will render them wrapped in `<p>` tags
- `{{ form.as_ul }}` will render them wrapped in `<li>` tags
```htmlmixed=
<form method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Send e-mail">
</form>
```
* The `{% csrf_token %}` template tag introduces a hidden field with an autogenerated token to avoid cross-site request forgery (CSRF) attacks.
* The tag generates something similar to this
```htmlmixed=
<input type='hidden' name='csrfmiddlewaretoken' value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR'/>
```
### Django Model Forms
```python=
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ('created',)
def __str__(self):
return f"Comment by {self.name} on {self.post}"
```
* Form for the above model
```python=
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
```
* Each model field type has a corresponding default form field type. The way that you define your model fields is taken into account for form validation.
* By default, Django builds a form field for each field contained in the model.
* You can eiter tell the form which fields to pick with a `fields` list or which fields to `exclude`
#### Handeling model form in views
* There are two main steps involved in validating a ModelForm:
- Validating the form: This is done like a normal form checking the HTTP verb and using the `is_valid()` method to v alidate the data.
- Validating the model instance: This is done with `clean()`
```python=
from .models import Comment
from .forms import CommentForm
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
```
* Since every comment is associated with a post, we need to assign the current post to the comment.
```python=
# create comment object but not save it
new_comment = comment_form.save(commit=False)
# assign the current post to the comment
new_comment.post = post
# Save it to DB
new_comment.save()
```
##### Complete code snippet
```python=
def post_detail(request, year, month, day, post):
# post variable takes the Post model.
post = get_object_or_404(
Post,
slug=post,
status='published',
# notice double underscore
publish__year=year,
publish__month=month,
publish__day=day
)
# Why not directly use Comments model ?
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.post = post
new_comment.save()
# need to edit this to redirect it to the post detail page.
return redirect(post)
else:
comment_form = CommentForm()
return render(request,
'blog/post/detail.html',
{
'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form,
}
)
```
#### Handeling model form in templates
```html=
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
<p>
<!--
Dynamically binding the url to the namespace: `(namespace)blog:(url)post_share
`post.id` is the parameter to build the absolute URL.
-->
<a href="{% url 'blog:post_share' post.id %}">
Share this post
</a>
</p>
<!--
`pluralize template filter makes the count like as 0 comments or N comments
if `total_comments` value is other than 1 else it shown as "1 comment"
-->
{% with comments.count as total_comments %}
<h2>
{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
{% for comment in comments %}
<div class="comment">
<p class="info">
<!--
`forloop.counter`contains the counter value of the loop
we get the counter and display the comment name.
-->
Comment {{ forloop.counter }} by {{ comment.name }}
{{ comment.created }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>There are no comments yet.</p>
{% endfor %}
<!--
if `new_comment` object exists you display the sucess message.
if not redender the <p> tag so user can create the comment.
-->
{% if new_comment %}
<h2>Your comment has been added.</h2>
{% else %}
<!--
if not redender the <p> tag so user can create the comment.
-->
<h2>Add a new comment</h2>
<form method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Add comment"></p>
</form>
{% endif %}
{% endblock %}
```