How to add comments with ajax in django website? - django

How can I add comments in my Django website without refreshing the page?
Here are my codes:
VIEW.PY
#login_required
def comments(request, post_id):
"""Comment on post."""
post = get_object_or_404(Post, id=post_id)
user = request.user
comments = Comment.objects.filter(post=post).order_by('-date')#comment
if request.method == 'POST':#Comments Form
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.user = user
comment.save()
messages.success(request, 'Comment has been added successfully.')
return HttpResponseRedirect(reverse('core:comments',args=[post_id]))
else:
form = CommentForm()
template = loader.get_template('post/comments.html')
context = {'post':post,'form':form,'comments':comments,}
return HttpResponse(template.render(context, request))
COMMENTS.HTMl
Form section:
<div class='commentsection' >
<strong>
<form action="{% url 'core:comments' post.id %}" method='post' id=comment-form >
{% csrf_token %}
{% bootstrap_form form %}
</form>
<br/>
<br/>
</strong>
</div>
Comments section:
{% for comment in comments %}
<div class="card mb-3" style="width: 30rem;">
<div class="row no-gutters">
<small> <a style="color: black;" href="{% url 'core:user_profile' comment.user %}"><img src="{{comment.user.profile.profile_pic.url}}" width=50 height=50 class="profile-image img-circle " style="object-fit: cover; float: auto; "><strong style="font-size: 18px;" > #{{comment.user}} </strong></a> </small> <small><span class="glyphicon glyphicon-time"></span> {{ comment.get_date }}</small>
</div>
<div class="card-body" style="width:100%; padding-left:20%; margin-top:-8%; word-break: break-all; color:black; " >
<h5 class="card-text">
{{ comment.comment }}</h5>
<small > Replies [{{comment.total_replies}}] </small
</div>
</div>
</div>
<br/><br/>
{% empty %}
<center>
No commnets
</center>
{% endfor %}
My problems:
Add comments without refreshing the page.
Auto-add newly added comments in the comment section without refreshing the page.
Thanks in advance!

You've correctly surmised that this an AJAX problem, though you could nowadays use a websocket as well. Traditional django solution would be with an AJAX callback.Short of writing it for you, I can offer a pro-forma solution (outline):
Add a view to your site which returns in JSON (or XML or any other serialisable format) a list of new comments from a given datetime (which you pass to the view as a GET param, or POST param if you prefer).
Add Javascript to you page which periodically polls the server with new comment requests. if it gets any returned then it has to dynamically add those comments to the DOM for you.
Your second request, auto-adding new comments, is contingent on whether you want to use a websocket or not. If you use a websocket then yes, the server can inform your page through that socket when new comments arrive. But if you want to stick with AJAX, then it is contingent on the polling interval (which is one big reason websockets were developed and are useful).
There are endless tutorials and how-tos online about writing the Javascript to fetch data and dynamically update the DOM, as there on how to use websockets.

Related

The right way to dynamically add Django formset instances and POST usign HTMX?

I'm making a form with a nested dynamic formset using htmx i (want to evade usign JS, but if there's no choice...) to instance more formset fields in order to make a dynamic nested form, however when i POST, only the data from 1 instance of the Chlid formset (the last one) is POSTed, the rest of the form POSTs correctly and the Child model gets the relation to the Parent model
I read the django documentation on how to POST formset instances and tried to apply it to my code, also i got right how to POST both Parent and Child at the same time. For the formsets i'm making a htmx get request hx-get to a partial template that contains the child formset and that works great, the only problem is that this always returns a form-0 formset to the client side, so for the POST the data repeats x times per field and only takes the data placed in the last instance, however i tried to change the extra=int value on my formset to get more forms upright, this gave the expected result, one Child instance per form in extra=int, so my problem is up with htmx and the way i'm calling the new Child formset instances.
here's my code. (i plan to nest more child formsets inside this form so i call this sformset for conveniece)
****views.py****
def createPlan(request):#Requst for the Parent form
form = PlanForm(request.POST or None)
sformset = StructureFormset(request.POST or None) #Nesting the Child formset
context = {
'form':form,
'sformset':sformset,
}
if request.method == 'POST':
print(request.POST)
if form.is_valid() and sformset.is_valid():
plan = form.save(commit=False)
print(plan)
plan.save()
sform = sformset.save(commit=False)
for structure in sform:
structure.plan = plan
structure.save()
return render(request, 'app/plan_forms.html', context)
def addStructure(request):
sformset = StructureFormset(queryset=Structure.objects.none())#add a empty formset instance
context = {"sformset":sformset}
return render(request, 'app/formsets/structure_form.html', context)
****forms.py****
StructureFormset = modelformset_factory(Structure,
fields = (
'material_type',
'weight',
'thickness',
'provider'
))
****relevant part for plan_forms.html template****
<form method="POST">
{% csrf_token %}
<div class="col-12 px-2">
<div class="row px-3 py-1">
<div class="col-3 px-1">{{ form.format }}</div>
<div class="col-3 px-1">{{ form.pc }}</div>
<div class="col-3 px-1">{{ form.revission }}</div>
<div class="col-3 px-1">{{ form.rev_date }}</div>
</div>
<div class="row px-3 py-1">
<div class="col-3 px-1">{{ form.client }}</div>
<div class="col-3 px-1">{{ form.product }}</div>
<div class="col-3 px-1">{{ form.gp_code }}</div>
<div class="col-3 px-1">{{ form.code }}</div>
</div>
</div>
<div>
<table>
<tbody style="user-select: none;" id="structureforms" hx-sync="closest form:queue">
<!--Structure formset goes here-->
</tbody>
<tfoot>
<a href="" hx-get="{% url 'structure-form' %}" hx-swap="beforeend" hx-target="#structureforms">
Add structure <!--Button to call structure formset-->
</a>
</tfoot>
</table>
</div>
<div class="col-12 px-2">
<div class="row px-4 py-1">{{ form.observation }}</div>
<div class="row px-4 py-1">{{ form.continuation }}</div>
<div class="row px-4 py-1">{{ form.dispatch_conditions }}</div>
<div class="row px-3 py-1">
<div class="col-6 px-1">{{ form.elaborator }}</div>
<div class="col-6 px-1">{{ form.reviewer }}</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
****formsets/structure_form.html****
<tr>
<td class="col-12 px-1">
{{ sformset }}
</td>
</tr>
**** relevant urls.py****
urlpatterns = [
path('create_plan/', views.createPlan, name='create_plan'),
path('htmx/structure-form/', views.addStructure, name='structure-form')]
Additionally, the form that i built in admin.py using fields and inlines is just exactly what i want as the raw product (except for the amount of initial formsets and styles)
To summarize the problem: At present, your code successfully brings in the new formset, but each new formset comes with a name attribute of form-0-title (ditto for id and other attributes). In addition, after adding the new formset with hx-get the hidden fields originally created by the ManagementForm will no longer reflect the number of formsets on the page.
What's needed
After a new formset is added to the site, here's what I think needs to happen so Django can process the form submission.
Update the value attribute in the input element with id="id_form-TOTAL_FORMS" so the number matches the actual number of formsets on the page after hx-get brings in the new formset.
Update the name and id of the new formset from form-0-title to use whatever number reflects the current total number of formsets.
Update the labels' for attributes in the same way.
You can do this with Javascript on the client side. Alternatively, you can do effectively the same thing with Django on the server side and then htmx can be the only javascript needed to do the rest. For that, I have used empty_form to create the html content of a formset which can be altered as needed. That work is shown in the build_new_formset() helper, below.
Example
Here's what I have working:
forms.py
from django import forms
from django.forms import formset_factory
class BookForm(forms.Form):
title = forms.CharField()
author = forms.CharField()
BookFormSet = formset_factory(BookForm)
views.py
from django.utils.safestring import mark_safe
from app2.forms import BookFormSet
def formset_view(request):
template = 'formset.html'
if request.POST:
formset = BookFormSet(request.POST)
if formset.is_valid():
print(f">>>> form is valid. Request.post is {request.POST}")
return HttpResponseRedirect(reverse('app2:formset_view'))
else:
formset = BookFormSet()
return render(request, template, {'formset': formset})
def add_formset(request, current_total_formsets):
new_formset = build_new_formset(BookFormSet(), current_total_formsets)
context = {
'new_formset': new_formset,
'new_total_formsets': current_total_formsets + 1,
}
return render(request, 'formset_partial.html', context)
# Helper to build the needed formset
def build_new_formset(formset, new_total_formsets):
html = ""
for form in formset.empty_form:
html += form.label_tag().replace('__prefix__', str(new_total_formsets))
html += str(form).replace('__prefix__', str(new_total_formsets))
return mark_safe(html)
Note re: build_new_formset() helper: formset.empty_form will omit the index numbers that should go on the id, name and label attributes, and will instead use "__prefix__". You want to replace that "__prefix__" part with the appropriate number. For example, if it's the second formset on the page its id should be id_form-1-title (changed from id_form-__prefix__-title).
formset.html
<form action="{% url 'app2:formset_view' %}" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<p>{{ form }}</p>
{% endfor %}
<button type="button"
hx-trigger="click"
hx-get="{% url 'app2:add_formset' formset.total_form_count %}"
hx-swap="outerHTML">
Add formset
</button>
<input type="submit" value="Submit">
</form>
formset_partial.html
<input hx-swap-oob="true"
type="hidden"
name="form-TOTAL_FORMS"
value="{{ new_total_formsets }}"
id="id_form-TOTAL_FORMS">
<p>{{ new_formset }}</p>
<button type="button"
hx-trigger="click"
hx-get="{% url 'app2:add_formset' new_total_formsets %}"
hx-swap="outerHTML">
Add formset
</button>
Note re: the hidden input: With every newly added formset, the value of the input element that has id="id_form-TOTAL_FORMS" will no longer reflect the actual number of formsets on the page. You can send a new hidden input with your formset and include hx-swap-oob="true" on it. Htmx will then replace the old one with the new one.
Docs reference: https://docs.djangoproject.com/en/4.1/topics/forms/formsets/

How Do You Trigger HTMX Page Refresh After User Updates Any Part of The Page?

I have been working with HTMX and it's pretty cool compared to the dreaded formsets and Javascript. I have it working....My only issue is when the user updates the form anywhere...you have to manually refresh the page to reset the list of todos. My issue is identical to this one...https://stackoverflow.com/questions/66664407/dynamically-update-table-when-creating-new-enty-using-htmx but there is no resolution listed.....
Here's a quick overview of my code...
My view...
def create_to_do(request):
user = User.objects.get(id=request.user.id)
to_dos = NewToDo.objects.filter(created_by=user)
form = ToDoForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
to_do = form.save(commit=False)
to_do.created_by = user
to_do.creation_date = timezone.now()
to_do.save()
return redirect("MyToDoList:detail-to-do", pk=to_do.id)
else:
return render(request, "partials/to_do_form.html", {
"form":form
})
context = {
"form": form,
"user": user,
"to_dos": to_dos,
}
return render(request, "create_to_do.html", context)
Partial detailview....
<button class="button35" hx-get="{% url 'MyToDoList:update-to-do' to_do.id %}" hx-swap="outerHTML">
Update
</button>
<button class="button34" hx-post="{% url 'MyToDoList:delete-to-do' to_do.id %}" hx-swap="outerHTML">
Delete
</button>
</div>
Partial todo form....
<div hx-target="this" hx-swap="outerHTML" class="">
<form method="POST">
{% csrf_token %}
{% if to_do %}
<button class="button35" hx-post="{% url 'MyToDoList:update-to-do' to_do.id %}">
Save
</button>
<button class="button34" hx-get="{% url 'MyToDoList:detail-to-do' to_do.id %}">
Cancel
</button>
</div>
{% else %}
<button class="button35" hx-post=".">
Save
</button>
</div>
{% endif %}
</form>
</div>
My main create form html..
<button class="button36" hx-get="{% url 'MyToDoList:create-to-do-form' %}" hx-swap="beforeend" hx-target="#bookforms">Add Form</button>
<div id="bookforms" class=""></div>
<div class="">
{% if to_dos %}
{% for to_do in to_dos %}
{% include "partials/to_do_detail.html" %}
{% endfor %}
{% endif %}
After a day of playing and pulling my hair out..it's all working as I want...I just need to figure out how to incorporate a dynamic page load if anything changes so that the entire page gets reloaded....so that the records get sorted according to my number field...
Thanks in advance for any thoughts or suggestions.
So thanks to a kind soul on Facebook....I added this Javascript to my code and it works.
$(document).on("click", ".yourclass", function(){
location.reload();
});
In case you need this, I had the problem when I have to update two/mutliple parts of the page after post.
I manage to solve it by trigger_client_event
Thanks to this issue: How do I manually trigger a hx-get event of an element from JavaScript
HTMX rocks!

How can a page stay where it is instead of refreshing and going to the top after interact with a domestic link such as 'edit'?

So I've built a Django forum, just added in the comment function. Here's a minor annoying thing needs to be sorted...
Problem:
When editing a comment, after pressing edit button/link, the page naturally refreshes itself, going to the top of the page instead of, what I hope for, staying right where it was... This isn't nice or friendly at all...
Here's the code:
url.py
app_name = 'forum'
urlpatterns = [
path('topic_<int:topic_pk>/<int:post_pk>/edit_<int:comment_pk>/', views.post, name='edit_comment'),
]
views.py
def post(request, topic_pk, post_pk, comment_pk=None):
'''displaying one post under a topic and all the comments under this post'''
# get existing topic and post
topic = get_object_or_404(Topic, pk=topic_pk)
try:
post = topic.post_set.get(pk=post_pk)
except:
raise Http404
# get all the comments under this post
comments = post.comment_set.order_by('-pk')
# deal with comment editing
if comment_pk != None:
comment_to_be_edited = get_object_or_404(Comment, pk=comment_pk)
if 'delete' in request.POST:
comment_to_be_edited.delete()
return redirect('forum:post', topic_pk, post_pk)
# get form with existing data ready to be rendered or edited/updated
edit_comment_form = CommentForm(instance=comment_to_be_edited)
if 'update' in request.POST:
edit_comment_form = CommentForm(instance=comment_to_be_edited, data=request.POST)
if edit_comment_form.is_valid():
edit_comment_form.save()
return redirect('forum:post', topic_pk, post_pk)
# if not to delete or to update, simply render the existing form with comment data ready to be edited
return render(request, 'forum/post.html', {
'topic': topic,
'post': post,
'comments': comments,
'comment_to_be_edited': comment_to_be_edited,
'edit_comment_form': edit_comment_form})
# if not posting a new comment, simply render the form
if request.method != 'POST':
form = CommentForm()
else:
# deal with posting a new comment
form = CommentForm(data=request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.post = post
new_comment.author = request.user
new_comment.save()
return redirect('forum:post', topic_pk, post_pk)
# render the post and all the comments and the empty/error form
return render(request, 'forum/post.html', {
'topic': topic,
'post': post,
'comments': comments,
'form': form})
post.html
{% for comment in comments %}
<div class="comment mt-3 border p-3 rounded-lg shadow-sm" id="{{ comment.pk }}">
{% if comment == comment_to_be_edited %}
<div class="d-flex justify-content-between align-items-start border-bottom">
<p class="font-weight-bold">{{ comment.author }}</p>
<p class="font-weight-light">{{ comment.date_added }}</p>
</div>
<form action="{% url 'forum:edit_comment' topic.pk post.pk comment.pk %}" method="POST">
{% csrf_token %}
{% bootstrap_form edit_comment_form %}
<button name="update" class="btn btn-sm btn-info">update</button>
<a href="{% url 'forum:post' topic.pk post.pk %}"
class="btn btn-sm btn-outline-danger">cancel</a>
<button name="delete" class="btn btn-sm btn-danger float-right">delete</button>
</form>
{% else %}
<div class="d-flex justify-content-between align-items-start border-bottom">
<p class="font-weight-bold">{{ comment.author }}</p>
<p class="font-weight-light">{{ comment.date_added }}</p>
{% if comment.author == user %}
<a href="{% url 'forum:edit_comment' topic.pk post.pk comment.pk %}"
class="shadow-sm btn btn-sm btn-outline-secondary float-right px-3">edit</a>
{% endif %}
</div>
<p class="mt-4">{{ comment.content | linebreaks }}</p>
{% endif %}
</div>
{% empty %}
<p class="text-muted">No comment for this post yet. Anything you'd like to share?</p>
{% endfor %}
You can probably tell from the code that I'm quite new in programming.
Now I've added an id="{{ comment.pk }}'' for each comment div based on their primary key. I'm probably v close to the solution, yet I'm not sure how to reference a div id in the view function...
Solution I've tried:
If only I could just add in #<int:comment_pk> at the end the each url when edit is triggered, like so:
path('topic_<int:topic_pk>/<int:post_pk>/edit_<int:comment_pk>/#<int:comment_pk>', views.post, name='edit_comment'),
or simply
path('topic_<int:topic_pk>/<int:post_pk>/edit/#<int:comment_pk>', views.post, name='edit_comment'),
However, Django doesn't seem to recognise the hash tag in urlpatterns, instead it's showing %20 something in rendered url of the page after edit is pressed/triggered.
I've also desperately tried this, hoping it'd work:
# view function
# --snip--
return render(request, 'forum/post.html#comment.pk', context)
Please help. Thanks!!
Refreshing page on form submission is browser's natural behavior.
What you want is Ajax read this article to learn how to Ajaxify your forms https://realpython.com/django-and-ajax-form-submissions/
However, I would advise using https://intercoolerjs.org/ for basic ajax views.

Django: How to add comments under post

I have trouble adding comments under my posts on the website I'm creating using Django.
This is my story.html file, which is supposed to show the story title, the story itself, all the comments of the story and give users the ability to add a new comment. Although the form is shown, it is not usable. Even though I have added comments to the stories manually through admin, none of them is shown.
{% extends "pinkrubies/base.html" %}
{% block content %}
<div class="post-preview">
<h2 class="post-title"> {{ story.title }}</h2>
<p class="post-subtitle">
{{ story.story }}
</p>
</div>
<div class="post-preview">
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
</div>
{% if user_id %}
<div class="post-preview">
<form action="{% url 'pinkrubies:story' user.id story.id %}" method="post">
{% csrf_token %}
<div class="form-group">
<p class="post-title">
Comments
</p>
<textarea id="text" name="text"class="form-control" placeholder="Comment" rows="4">{{ comment.com }}
</textarea>
</div>
<button type="submit" class="btn btn-primary"> Submit </button>
</form>
</div>
{% else %}
<p class="post-meta">You must have an account to comment. Log in or Register</p>
{% endif %}
{% endblock %}
views.py
def story_view(request, user_id, story_id):
latest_comments = Comment.objects.order_by('-date')
if story_id is not None:
story = get_object_or_404(Story, pk=story_id)
else:
story = Story()
story.user_id = user_id
if request.method == 'POST':
story.title = request.post.get('title')
story.story = request.post.get('story')
story.date = timezone.now()
story.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {
'user_id': user_id,
'story_id': story_id,
'title': story.title,
'story': story,
'comments': story.comments,
'latest_comments': latest_comments
}
return render(request, 'pinkrubies/story.html', context)
def comment_view(request, comment, user_id):
latest_comments = Comment.objects.order_by('-date')
if request.method == 'POST':
comment.com = request.POST['com']
comment.date = timezone.now()
comment.save()
return HttpResponseRedirect(reverse('pinkrubies:story', args=(user_id,)))
else:
context = {'latest_comments': latest_comments}
return render(request, 'pinkrubies/story.html', context)
I am aware I have added the "latest_comments" in both views, I did that to see if any of it works and it doesn't. I'm not even sure that the comment_view is needed.
Also, when I try to submit a comment, this error is thrown:
AttributeError: 'WSGIRequest' object has no attribute 'post'
in story_view story.title = request.post.get('title')
Any help is welcome!
Thank you !!!
First of all, if you want to get something from POST, you should use request.POST.get('title'); although I would rather use a Form and let it handle the request.
At a first glance, this:
{% for com in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
Sould be
{% for comment in latest_comments %}
<div class="post-preview">
<p class="post-subtitle"> {{ comment.com }} </p>
</div>
{% endfor %}
In your code, you're using com to iterate through latest_comments, yet you try to use comment to access com attribute (not sure how your Comment model looks like though)
It's accessed via request.POST. WSGIRequest object does not have a post field it has POST field.
Also, you are not sending the fields you are attempting to read from request.
...
story.title = request.POST.get('title')
story.story = request.POST.get('story')
...
only field you are sending is called text and it should be accessed like this
text = request.POST.get('text')
also template has more errors please check your code once again.

Trouble getting Django ModelForm inside modal to work

OK - I'm positive that the issue is that I have some fundamental understanding of how forms work in Django, so 30,000 ft conceptual explanations are welcome, in addition to code fixes!
I'm trying to run my site from (mostly) a single view (and single template), with modal popups to view info (working) and to edit or add it (not working). I can see the form, fill it out, and click submit, at which point the modal closes, but no new Courses show up in the admin view.
I'll confine this to a single model - my simplest - for the time being:
models.py:
class Course(models.Model):
Name = models.CharField(max_length=30,unique=True)
Active = models.BooleanField(default=True)
def __unicode__(self):
return u'%s' % (self.Name)
views.py
def IndexView(request,Course_id,Section_id):
template_name = 'gbook/index.html'
print Course_id,Section_id
this_course = Course.objects.get(pk=Course_id)
active_courses = Course.objects.all().filter(Active=True).exclude(pk=Course_id)
section_list = Section.objects.all().filter(course=this_course)
if len(section_list) >1:
multi_section = True
else:
multi_section = False
active_section = Section.objects.get(pk=Section_id)
roster = Student.objects.all().filter(sections__in=[active_section])
announcement_list = Announcement.objects.all().filter(sections__in=[active_section])
courseaddform = CourseAddForm()
context = {'active_courses':active_courses, 'this_course': this_course,
'active_section':active_section, 'section_list':section_list,
'roster':roster, 'multi_section':multi_section,
'announcement_list':announcement_list, 'courseaddform':courseaddform}
return render(request,'gbook/index.html', context)
forms.py
class CourseAddForm(forms.ModelForm):
class Meta:
model = Course
fields = ['Name', 'Active']
templates/index.html
...
<li class="dropdown">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span><span class="caret"></span>
<ul class="dropdown-menu">
<li><a data-toggle="modal" data-target="#SectionRosterModal">Roster</a></li>
<li><a data-toggle="modal" data-target="#AnnouncementModal">Announcements</a></li>
<li><a data-toggle="modal" data-target="#CourseAddModal">CourseAdd</a></li>
</ul>
</li>
...
<!-- COURSE ADD MODAL -->
<div class="modal fade" id="CourseAddModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header" style="padding:5px 10px;">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4>Add Course</h4>
</div>
<div class="modal-body" style="padding:10px 10px;">
<form data-parsley-validate method="post" id="courseaddform" action="" enctype="multipart/form-data"
data-parsley-trigger="focusout">
{% csrf_token %}
{{ courseaddform.as_p }}
<p id="login-error"></p>
<input type="submit" class="btn btn-info submit" name="AddCourse" value="Add Course" />
</form>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
...
I think that there's supposed to be a POST command in there somewhere, but I really don't have a good handle on the process here. Thanks for the help!
It looks like you are not doing anything with the form data when you post back, you need to process the form if the request method is a POST
def IndexView(request, ...):
if request.method == "GET":
... do what you are doing now and return
elif request.method == "POST":
cf = CourseAddForm(request.POST)
if cf.is_valid():
...do stuff with cf.cleaned_data <---- this is a dict
return ....
You are doing the same thing for GET and POST requests right now and neither deals with the submitted form
see here for more details
https://docs.djangoproject.com/en/1.10/topics/forms/#the-view
EDIT #1:
The POST should be a standard HTTP POST request back to the same URL. Just set the method tag as you are now and action="." (or a URL lookup in the template).
You need to return a valid HTTPResponse object but the normal case when dealing with a form is to return a HTTPResponseRedirect(...some url...) if the form is valid. In the case of a single page app, if you reload the same page you need to do everything you did in the request.method == "GET" so maybe return a HTTPResponseRedirect back to the same URL. In this case, I would look at the django messages framework and add a message to the context saying the form was submitted successfully and display the message in the reloaded page (you should always check if there is a message to display anyway when using the messages framework so this will not break the case where you are loading the page for the first time)
https://docs.djangoproject.com/en/1.9/ref/contrib/messages/