I am trying to display a comment form on a page. So far I have created a link and I want that each time that link is clicked it displays the form on the same page as where the link is but my problem here is that the link redirects me to another page, which I don't want.
urls.py
url(r'^all/$', 'posts.views.articles'),
url(r'^get/(?P<post_id>\d+)/$', 'posts.views.article'),
url(r'^articles/$', 'posts.views.create'),
url(r'^like/(?P<post_id>\d+)/$', 'posts.views.like_article'),
url(r'^article/(?P<post_id>\d+)/$', 'posts.views.add_comment'),
views.py
def articles(request):
args = {}
args.update(csrf(request))
args ['posts'] = post.objects.filter(user = request.user)
args ['full_name'] = User.objects.get(username = request.user.username)
args ['form'] = PostForm()
return render_to_response('articles.html', args)
def article(request, post_id=1):
return render(request, 'article.html',
{'post': post.objects.get(id=post_id) })
def add_comment(request, post_id):
a = post.objects.get(id=post_id)
if request.method == "POST":
f = CommentForm(request.POST)
if f.is_valid():
c = f.save(commit=False)
c.pub_date = timezone.now()
c.article = a
c.save()
messages.success(request, "You Comment was added")
return HttpResponseRedirect('/posts/get/%s' % post_id)
else:
f = CommentForm()
args = {}
args.update(csrf(request))
args['post'] = a
args['form'] = f
return render_to_response('article.html', args)
#return HttpResponseRedirect('/posts/all')
article.html
<h2>Comments</h2>
{% for c in post.comment_set.all %}
<p>{{c.name}} : {{c.body}}</p>
{% endfor %}
<form action="/posts/article/{{post.id}}/" method="post">{% csrf_token %}
<ul>
{{form.as_ul}}
</ul>
<input type="submit" name="submit" value="Post Comment">
</form>
{% endblock %}
As from your question you want submit a comment in your article and when you submit the comment you want to redirect it to the same article page... If you are willing to do this then here is example:
First create a comment submit form either using model form or just form:
class CommentCreationForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('comment_body',) # Set your field for comment
Now pass this form as context in ur article view. Like you did above.
def articles(request):
args = {}
args.update(csrf(request))
args ['posts'] = post.objects.filter(user = request.user)
args ['full_name'] = User.objects.get(username = request.user.username)
args ['comment_form'] = CommentCreationForm
return render_to_response('articles.html', args)
Your article.html
<h2>Comments</h2>
{% for c in post.comment_set.all %}
<p>{{c.name}} : {{c.body}}</p>
{% endfor %}
<form action=""{% url "comment_submit" post.id %}"" method="get">{% csrf_token %}
<ul>
{{form.as_ul}}
</ul>
<input type="submit" name="submit" value="Post Comment">
</form>
{% endblock %}
Catch the url with url(r'^comment/(?P<pk>\d+)/',CommentSubmitView, name="comment_submit"), and write a view.
def AnswerSubmitView(request, pk):
post = Post.objects.get(id=pk) # Get what you have set for your article
comment_text = request.GET.get('comment_body',False)
com = Comment()
post = post # or anything that you have named for your article..
com.comment_body = comment_text
com.save()
return HttpResponseRedirect('/post/%s' % pk) # Your url for your article I guess...
Enjoy...
Use an ajax call to fetch the form from the server without refreshing the page. This requires jQuery. Replace the placeholder selectors I've used with whatever you need for your app. I'd recommend wrapping all of article.html in a div and give that an id tag (and refer to this tag where I use '#form-id' selector below), so you know when the form is already displayed and you can access the entire chunk.
Also note that I'm not entirely sure how to get the html from render_to_response. Just figure out what kind of object is sent back to the ajax caller and how to get the html from that object. Shouldn't be hard.
Adapt and add the following to the bottom of the template containing the link to add the form
<script>
var showForm = function(url) {
$.ajax({
type: 'GET',
dataType: 'json',
url: url,
success: function(data, status, xhr) {
// Not positive if this is how things work with render_to_response
// I usually use render_to_string for this and just return pure HTML
$('#div-to-display-form-in').append(data);
},
error: function(error) {
// Handle error
}
});
}
$(document).ready(function() {
$('#link-to-show-form').click(function(event) {
event.preventDefault();
// The conditionals check if form is already showing
// If form already showing and link clicked again, form is removed
if ($('#form-id').length === 0) {
showForm($(this).attr('href'));
} else {
$('#form-id').remove();
}
});
});
</script>
Related
I am creating a multi-choice quiz app, I have created a view which shows the question and 4 option. I have given radio button to each option but is giving me this error:
MultiValueDictKeyError at /quiz/2/11/ 'choice'
views.py
def question_detail(request,question_id,quiz_id):
q = Quiz.objects.get(pk=quiz_id)
que = Question.objects.get(pk=question_id)
ans = que.answer_set.all()
selected_choice = que.answer_set.get(pk=request.POST['choice'])
if selected_choice is True:
come = que.rank
came = come + 1
later_question = q.question_set.get(rank=came)
return render(request,'app/question_detail.html',{'que':que , 'later_question':later_question, 'ans':ans})
else:
come = que.rank
later_question = q.question_set.get(rank=come)
return render(request, 'app/question_detail.html', {'que': que, 'later_question': later_question, 'ans': ans})
question_detail.html
<form action="{% 'app:detail' quiz_id=quiz.id question_id=que.id %}" method="post">
{% csrf_token %}
{% for choice in que.answer_set.all %}
<input type="radio" name="choice" id="choice{{forloop.counter}}" value="{{choice.id}}">
<label for="choice{{forloop.counter}}">{{choice.answer}}</label>
{% endfor %}
</form>
Okay like I said in my comment, you're most likely getting that error because the POST object will be empty during a normal GET request. So you should wrap everything that's meant to happen after a request in an IF block:
if request.method === 'POST':
selected_choice = que.answer_set.get(pk=request.POST['choice'])
# Every other post-submit task
You should always check for the POST method in your views if you're expecting form data. Others have answered this more in-depth before so I would just direct you there:
What does request.method == "POST" mean in Django?
I'm trying to have all location functionality on one page, such as
get users location, post it to the database, then filter results based on a user-inputted kilometre radius value.
I am getting MultiValueDictKeyError at /connect/ 'latitude' because the POST request is going to
location = Location(latitude=request.POST['latitude'], longitude=request.POST['longitude'], user = request.user)
when it should be going to
if request.POST['radius']:
radius_km = request.POST.get('radius', 0)
I researched how to stop this happening and saw I could do if request.POST['radius']: to direct the post request to the right function.
This hasn't helped with the error unfortunately.
Am I missing something?
views.py
class ConnectView(View):
template_name = 'connect/home.html'
def get(self, request, *args, **kwargs):
f = ProfileFilter(request.GET, queryset=Profile.objects.exclude(user=request.user))
context = {
'users': User.objects.exclude(username=request.user),
'friends': Friend.objects.filter(current_user=request.user),
'filter': f,
}
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
location = Location(latitude=request.POST['latitude'], longitude=request.POST['longitude'], user = request.user)
location.save()
if request.POST['radius']:
radius_km = request.POST.get('radius', 0)
queryset = User.objects.annotate(
radius_sqr=pow(models.F('loc__latitude') -
request.user.loc.latitude, 2) + pow(models.F('loc__longitude') -
request.user.loc.longitude, 2)
).filter(
radius_sqr__lte=pow(int(radius_km) / 9, 2)
).exclude(username=request.user)
context = {'users': queryset}
return JsonResponse({'message': 'success'})
home.html
<script>
var pos;
var $demo;
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
$demo.text("Geolocation is not supported by this browser.");
}
}
function showPosition(position) {
pos = position;
var { latitude, longitude } = pos.coords;
$('#btn_submit').attr("disabled", null);
}
$(document).ready(function() {
$demo = $("#demo");
$('#btn_submit').on('click', function() {
var data = pos.coords;
data.csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
$.post('', data, function() {
alert("Location Confirmed!");
});
});
});
</script>
<h1>Connect with people.</h1>
<!-- GET window.location IP Address / lat lon coordinates -->
<p id="demo"></p>
<button onclick="getLocation()" class="btn btn-warning" id="confirm">1. Find Location</button>
<button type="submit" id="btn_submit" name="btn_submit" class="btn btn-success" disabled>2. Submit Location </button>
<!-- filter by profile attributes -->
<form method="GET">
{{ filter.form }}
<button type="submit" class="small">Search.</button>
</form>
<!-- enter radius to filter by location-->
<form method="POST">
{% csrf_token %}
<input type="number" name="radius">
<input type="submit" value="filter by kilometers">
</form>
In your post method, you have added request.POST['latitude'], which you might not be passing with the post method. And your if.. condition comes after that. Therefore Django gives you error, because it doesn't find any parameter with this name, and will stop the further execution.
So, on post request if you are not passing latitude longitude parameters try writting it as:
request.POST.get('latitude', None)
or,
put your if.. condition before accessing latitude and longitutde parameters.
request.POST is just a python dictionary (almost, it can have multiple values for the same key, hence the class name MultiValueDict). So just treat it as a normal dictionary object:
dict[key] raises a KeyError if the key doesn't exist
dict.get(key) returns the value for key or None if it doesn't exist.
dict.get(key, default) returns the value for key of default if the key doesn't exist.
request.POST['latitude'], request.POST['longitude'] and request.POST['radius'] will throw this exception if these keys aren't in the POST parameters. Always use request.POST.get(param_name) when checking what is being submitted.
In your case, you should also add an if 'latitude' in request.POST and 'longitude' in request.POST clause before doing anything with latitude and longitude.
I've created a form which by submit uploads an item to the database. The problem is that if I press f5 it'll submit the form again, because of the URL is now different.
I have these two url patterns
urlpatterns = [
url(r'(?i)^CMS/$', views.CMS, name='CMS'),
url(r'^createItem/$', views.createItem, name='createItem')
]
and my view looks like this
def CMS(request):
form = itemCreateForm()
context = {
'form' : form,
'message' : 'Content Manage Site'
}
return render(request, 'CMS.html', context)
def createItem(request):
f = itemCreateForm(request.POST)
if f.is_valid():
f.save()
pass
form = itemCreateForm()
context = {
'form' : form,
'message' : 'ItemCreated!'
}
return render(request, 'CMS.html', context)
the CMS.html
{% if message %}
{{ message }}
{% endif %}
<div class='newItemFields'>
<form action="{% url 'kar:createItem' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
</div>
my form
class itemCreateForm(ModelForm):
class Meta:
model = item
fields = ['name', 'type', 'price']
I start at homepage/CMS/ and fill in the form and press submit, and view function createItem runs and creates and saves the object in the database. And sends the user to homepage/CMS/createItem. And now everytime the user press f5 the createItem function will run again and insert another object into the database with the same values as the previous one, even though the input fields are empty (can't wrap my head around that).
I also twice write form = itemCreateForm() which I believe is dubious?
What I'd like to do is after createItem is run, it should send the user back to homepage/CMS/ and not homepage/CMS/createItem. Would that be the proper way to do it? Or is there a smart way of doing this.
At the end of your createItem function, you are rendering HTML of the page rather than redirecting. Instead, you need to do
return HttpResponseRedirect(reverse('kar:index'))
You will need to import HttpResponseRedirect and reverse which is used to resolve the URL through its name.
Check this out: https://docs.djangoproject.com/en/1.10/topics/forms/#the-view
What I'd like to do is after createItem is run, it should send the
user back to homepage/CMS/ and not homepage/CMS/createItem. Would that
be the proper way to do it? Or is there a smart way of doing this.
That would indeed be the proper and smart way to do it. Have one view handle both GET and POST and then redirect after successful form submission. This ensures that the user can't resubmit the form merely by refreshing. And you address your concern about repeating your code.
urlpatterns = [
url(r'(?i)^$', views.index, name='index'),
url(r'^createItem/$', views.createItem, name='createItem')
]
Then combine your views
def createItem(request):
if request.method == 'POST':
f = itemCreateForm(request.POST)
if f.is_valid():
f.save()
return HttpResponseRedirect('/homepage/CMS/')
else :
form = itemCreateForm()
context = {
'form' : form,
'message' : 'Content Manage Site'
}
return render(request, 'CMS.html', context)
Note that the code is now shorter, it gives proper feedback to the user when the form is not valid. And you can't refresh to submit the for twice. We need a small change to the template
<div class='newItemFields'>
<form action=method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
</div>
The message display part isn't needed anymore
I added a newsletter sign-up form to the footer area of my site and such had to use an inclusion_tag because I couldn't bind it to a view. It works well and as expected, but I have a strange thing happening that I apparently am not smart enough to figure out myself :)
After the form is submitted, I receive the email confirmation, but two things happen:
The Django Success Message doesn't appear until after I manually refresh the page.
Where my form sits, there are syntax 'Missing Variable' errors. I included a screenshot for reference and my form code is below. The form fields re-appear and errors go away after refreshing the page again.
home_tags.py
#register.inclusion_tag('pages/tags/footer_newsletter_signup.html', takes_context=True)
def footer_newsletter_signup(context):
request = context['request']
title = 'Newsletter Signup'
form = MailingListForm(request.POST or None)
if form.is_valid():
mailing_list_full_name = form.cleaned_data.get('mailing_list_full_name')
mailing_list_phone = form.cleaned_data.get('mailing_list_phone')
mailing_list_email = form.cleaned_data.get('mailing_list_email')
mailing_list_subject = 'Submission from Newsletter Signup'
mailing_list_message = 'Yes, please add me to marketing emails.'
from_email = settings.DEFAULT_FROM_EMAIL
recipient_list = [from_email, 'charles#studiorooster.com']
ctx = {
'mailing_list_subject': mailing_list_subject,
'mailing_list_full_name': mailing_list_full_name,
'mailing_list_email': mailing_list_email,
'mailing_list_phone': mailing_list_phone,
'mailing_list_message': mailing_list_message
}
message = get_template('pages/newsletter_signup_email.html').render(Context(ctx))
msg = EmailMessage(mailing_list_subject, message, to=recipient_list, from_email=from_email)
msg.content_subtype = 'html'
msg.send()
messages.success(request, "Thank you, you've been added to our list.")
return HttpResponse('/')
context = {
'form': form,
'title': title,
}
return context
footer_newsletter_signup.html
<form action='' method='POST' role='form' class="form-inline">
{% csrf_token %}
<div class="form-group">
{{ form.mailing_list_full_name }}
</div>
<div class="form-group">
{{ form.mailing_list_phone }}
</div>
<div class="form-group">
{{ form.mailing_list_email }}
</div>
<button class="button button-lg button-square button-pasific hover-ripple-out" type='submit'>Subscribe</button>
</form>
Then I just add the tag to my template like:
{% footer_newsletter_signup %}
Answering this
Ok, so here is where I am confused. I have a dozen views and this form is a Call-to-Action form that sits at the top of the footer. How do I bind this form to every view without repeating the code everywhere? Thank you for your help.
You need to create separate view to handle this form and provide action param in form tag pointing to this view.
Here is general idea, code my not work
#template
<form action='{% url "send-mail" %}' method='POST' role='form' class="form-inline">
...
#views
def send_mail(request):
form = MailingListForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
mailing_list_full_name = form.cleaned_data.get('mailing_list_full_name')
mailing_list_phone = form.cleaned_data.get('mailing_list_phone')
mailing_list_email = form.cleaned_data.get('mailing_list_email')
mailing_list_subject = 'Submission from Newsletter Signup'
mailing_list_message = 'Yes, please add me to marketing emails.'
from_email = settings.DEFAULT_FROM_EMAIL
recipient_list = [from_email, 'charles#studiorooster.com']
ctx = {
'mailing_list_subject': mailing_list_subject,
'mailing_list_full_name': mailing_list_full_name,
'mailing_list_email': mailing_list_email,
'mailing_list_phone': mailing_list_phone,
'mailing_list_message': mailing_list_message
}
message = get_template('pages/newsletter_signup_email.html').render(Context(ctx))
msg = EmailMessage(mailing_list_subject, message, to=recipient_list, from_email=from_email)
msg.content_subtype = 'html'
msg.send()
messages.success(request, "Thank you, you've been added to our list.")
return HttpResponse('/')
#tags
#register.inclusion_tag('pages/tags/footer_newsletter_signup.html', takes_context=True)
def footer_newsletter_signup(context):
title = 'Newsletter Signup'
form = MailingListForm()
context = {
'form': form,
'title': title,
}
return context
#url
url('r^send-mail/$', send_mail, name='send-email')
in views:
return render_to_response("main.html", RequestContext(request, {'form':form, "result":result}))
in template i have this jquery function:
$('#submitButton').click(function(e) {
e.preventDefault();
var dataPosted = $("#mainSubmit").serialize();
$.ajax({
type: "POST",
data: dataPosted,
url: 'main/',
success: function(data) {
$("#mainDiv").html(data);
$(".response").html({{ result }});
$(".response").show();
}
});
});
});
<div id="mainDiv" class="part">
<form id="mainSubmit" action="main/" method="POST" name="submitForm">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.as_p }}
<input type="submit" value="Submit" id="submitButton"/>
<div class="response" style="display: none;"></div>
</form>
</div>
but it seems that data can't be assigned to response div like this(it seems data is not defined). So i don't know what is the way to send data to template. if i use Httpresponse(result) in views, then i can't have my form refreshed, and only i can display in response div data i send from view. so what is the way?
EDIT:
This is my views. before, i didn't put else for the condition if form.is_valid(): , but here i use, because i think if i don't do this, it might cause some probables. i don't know what is the best way.
def mainFunc(request):
if request.is_ajax():
if request.method == 'POST':
form = mainForm(request.POST)
if form.is_valid():
// process the form
result = "successful"
to_json = {'form':form, 'result':result}
return HttpResponse(json.dumps(to_json), mimetype='application/json')
else:
result = ""
to_json = {'form':form, 'result':result}
return HttpResponse(json.dumps(to_json), mimetype='application/json')
else:
form = mainForm()
return render_to_response('main.html', RequestContext(request, {'form':form}))
else:
return render_to_response("ajax.html", {}, context_instance=RequestContext(request))
You need to return a response in format like JSON
You can use this snippet or more simple code like this:
from django.utils import simplejson
to_json = {'form':form, "result":result}
return HttpResponse(simplejson.dumps(to_json), mimetype='application/json')
Then you will be able to use data.result and data.form in your JS code.
If you use the same view for ajax and non-ajax call you can check for it with request.is_ajax()
Also you will not be able to use template tags and filters in your JS callback. So you need to pre-render your form before sending it via JSON
So the final code:
to_json = {'form':form, "result":result}
if request.is_ajax():
to_json['form'] = to_json['form'].as_p()
return HttpResponse(simplejson.dumps(to_json), mimetype='application/json')
else:
render_to_response("main.html", RequestContext(request, {'form':form, "result":result}))
Edit I assume that ajax.html is the template for the whole page and main.html is the template for mainDiv part of the page
So in is_ajax() part of you view you can return the data like this.
to_json = {}
to_json['form'] = render_to_string('main.html', {'form': form}, context_instance=RequestContext(request))
to_json['result'] = result
return HttpResponse(json.dumps(to_json), mimetype='application/json')
And you always return data like this, both for GET and POST AJAX calls
And in JS you get data like this:
$("#mainDiv").html(data.form);
$(".response").html(data.result);
$(".response").show();