Can I add a form in django html?

Viewed 74

I want to add comments form a specific html which has it's own views and models and I do not want to create a new html file like comment.html which will only display the form and its views. I want users to be able to comment right underneath a post, so that users don't have to click a button such as "add comment" which will take them to a new page with the "comment.form" and then they can comment. But I'm stuck. I can add comments manually from the admin page and it's working fine, but it seems that I have to create another url and html file to display the comment form and for users to be able to add comments(btw I'm trying to build a sports related website). Thanks in advance!

My models.py:

class Transfernews(models.Model):
    player_name = models.CharField(max_length=255)
    player_image = models.CharField(max_length=2083)
    player_description = models.CharField(max_length=3000)
    date_posted = models.DateTimeField(default=timezone.now)

class Comment(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    transfernews = models.ForeignKey(Transfernews, related_name="comments", on_delete=models.CASCADE)
    body = models.TextField()
    date_added = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '%s - %s' % (self.transfernews.player_name, self.user.username)

My forms.py :

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ('body',)

My views.py :

def addcomment(request):
    model = Comment
    form_class = CommentForm
    template_name = 'transfernews.html'

My urls.py:

path('comment/', views.addcomment, name='comment'),

My transfernews.html:

<h2>Comments...</h2>

{% if not transfernew.comments.all %}
No comments Yet...
{% else %}
{% for comment in transfernew.comments.all %}
  <strong>
    {{ comment.user.username }} - {{ comment.date_added }}
  </strong>
  <br/>
  {{ comment.body }}
  <br/><br/>
  {% endfor %}
  {% endif %}
  <hr>
  <div>Comment and let us know your thoughts</div>
  <form method="POST">
    {% csrf_token %}
    <div class="bg-alert p-2">
    <div class="d-flex flex-row align-items-start"><textarea class="form-control ml-1 shadow-none textarea"></textarea></div>
    <div class="mt-2 text-right"><button class="btn btn-primary btn-sm shadow-none" type="submit"> <a href="{% url 'comment' %}"></a>
    Post comment</button><button class="btn btn-outline-primary btn-sm ml-1 shadow-none" type="button">Cancel</button></div>
    </div>
  </div>
  </form>
2 Answers

I interpret you question like you want a page with all transfer news and their respective comments as well as a comment-form under the old comment.

This is a common situation where using generic views can be beneficial, more specifically the FormView. The tricky part will be to restructure stuff so that you can link the Comment your CommentForm creates with the correct Transfernews and User.

So what I'd do is:

  1. Keep the Models you have.
  2. Edit the CommentForm class to also have the fields user and transfernews (see below).
  3. Create a new view inheriting from django.views.generic.FormView. You will need to change some built in methods. Mainly the get_context_data and the get_form_kwargs. The first so that you get all the information you need to show all transfernews and earlier comments, and the latter so you give the form all necessary information. You also need to override form valid to save the instance created. Something like this should work:
from django.forms import ModelForm
from django.views.generic import FormView
from transfer.models import Comment, Transfernews


class CommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ('body', 'user', 'transfernews')
        
    def __init__(self, *args, **kwargs):
        super(CommentForm, self).__init__(*args, **kwargs)


class TransfernewsView(FormView):
    template_name = 'transfernews.html'
    form_class = CommentForm
    success_url = '/transfers'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context.update({
            'transfernews': [
                {'news': transfer, 'comments': Comment.objects.all().filter(transfernews=transfer.id)}
                for transfer in Transfernews.objects.all()
            ]
        })
        return context

    def get_form_kwargs(self):
        form_kwargs = super().get_form_kwargs()
        if self.request.method in ('POST', 'PUT'):
            form_kwargs['data'] = form_kwargs['data'].copy()
            form_kwargs['data'].update({'user': self.request.user})
            
        return form_kwargs

    def form_valid(self, form):
        form.save()
        return super(TransfernewsView, self).form_valid(form)

  1. Update your urls.py so that you use your new view like so: path('transfers/', views.TransfernewsView.as_view(), name='transfers'),

  2. Update the HTML file so that each transfernews shows all comments and also has its own form. This form must contain both the body field and a hidden field with the transfernews.id. A skeleton is below.

<h1>Transfers</h1>

{% for transfer in transfernews %}
    Information on the transfer of player: {{ transfer.news.player_name }}
    ...
    {% for comment in transfer.comments %}
        <h5>Comment by {{ comment.user.username }}</h5>
        <div>{{ comment.body }}</div>
    {% endfor %}
    <from method='POST'>
         {% csrf_token %}
         <input id="transfernews" type="hidden" name="transfernews" value="{{ transfer.news.id }}">
         <input id="body" type="text" name="body">
         <input type="submit" value="Send comment"> 
    </form>
{% endfor %}

I hope this helps!

You should also investigate using the build in form fields when writing the HTML form. I.e. using {{ form.body }} instead of using <input id="body" type="text" name="body">. I saw you are using bootstrap. One option for automatic bootstrap styling is crispy-forms. You should look into that ;)

I'm making a few assumptions here so sorry if this suggestion is redundant but I think what you want is a function-based view which I think you have tried to use but you've mixed together elements of both a function and class-based view in

def addcomment(request):
    model = Comment
    form_class = CommentForm
    template_name = 'transfernews.html'

Have a look at this example, which of course doesn't fit all the needs of your code because I don't know all the details of your project but you should be able to see how a view-function can work in your case. I've called the view function display_transfer_news just to disassociate it with being a separate page for adding comments.

views.py

from django.shortcuts import render, redirect

from .forms import CommentForm
from .models import Transfernews

def display_transfer_news(request):
    # Grab all Transfernews objects in database
    transfernews = Transfernews.objects.all()

    # create a blank form for get requests
    form = CommentForm()

    # This block runs only when comment form is posted to this view
    if request.method == 'POST':
        # populate form object with data from the commentform
        form = CommentForm(request.POST)
        # If there are any errors in the form we don't redirect but just render the page with the form errors.
        if form.is_valid():
            # creates a comment in the database
            form.save()

            # If the save was successful we redirect to this same view which will grab the new comment we just made when calling Transfernews.objects.all() again
            return redirect(display_transfer_news)

    # format for render is render(request, template, context)
    return render(
        request,
        "transfernews.html",
        # This dictionary is the context object
        { 
            "comment_form": form,
            "transfer_news": transfernews
        }
    )

The corresponding path in urls.py would look something like this for example

path('whatever-your-transfernews--page-url-is', views.display_transfer_news)

and in your view, you'd display your comment form and transfernews objects using the names from that context dictionary from the view.

transfernews.html

{% if not transfer_news.comments.all %}
No comments Yet...
{% else %}
{% for comment in transfer_news.comments.all %}
  <strong>
    {{ comment.user.username }} - {{ comment.date_added }}
  </strong>
  <br/>
  {{ comment.body }}
  <br/><br/>
  {% endfor %}
  {% endif %}
  <hr>
  <div>Comment and let us know your thoughts</div>
  <form method="POST">
    {% csrf_token %}
    
    <!-- Haven't styled the form as it's just an example -->
    {{ comment_form.as_p }}

    <button class="btn btn-primary btn-sm shadow-none" type="submit">Post comment</button>
    </div>
  </div>
  </form>

From my perspective this is the direction I think you are trying to head in. No extra page for creating comments. Take from it what you will as it just a guide.

Related