# 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 %} ```