Django - passing data and redirecting between views - django

I'm making a project related to quizzes online and I'm stuck at the moment where I want to pass data from one view to another. My goal is to get a quiz name from the user in the form in one view and pass it to another view, after submitting a form.
I've made 2 views:
add() - which has the first form with the name for the quiz and redirects to add_questions with the name of the quiz
add_questions() - which captures the name of the submitted quiz and displays another form for the questions
views.py
#login_required
def add(request):
if request.method == 'POST':
QuizForm = QuizNameForm(request.POST)
if QuizForm.is_valid():
# create a quiz
new_quiz = QuizForm.save(commit=False)
new_quiz.owner = request.user
new_quiz.save()
request.session['quiz_id'] = new_quiz.id
print("Quiz saved, now I'm redirecting to add_questions")
return redirect(add_questions)
#return HttpResponseRedirect('add_questions') --- didn't work
#return render(request, 'quiz/add_questions.html', {}) --- didn't work
else:
QuizForm = QuizNameForm()
return render(request, 'quiz/add.html', {'quizForm': QuizForm})
#login_required
def add_questions(request):
print("Add questions here! I've captured a quiz named: {}".format(quiz.name))
quiz = DB_questions.objects.get(id=request.session['quiz_id'])
if request.method == 'POST':
print("ok, post method - cool")
#create a question/answer forms
else:
print("Got into else in add_questions view")
Question_form = QuestionForm()
return render(request, 'quiz/add_questions.html', {'quiz_name': quiz.name, 'question_form': Question_form })
My template for adding quiz name - templates/add.html
<div class="container">
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4">
<!-- Get name of the quiz-->
<form action="{% url 'add' %}" method="post">
{% csrf_token %}
{{ quizForm.name }}
<input class="btn btn-success w-100" type="submit" value="Proceed">
</form>
</div>
<div class="col-md-4"></div>
</div>
</div>
Template to display the name and question form - templates/add_question.html
<div class="row">
<div class="col-md-5"></div>
<h2 class="select_title"> Quiz: {{ quiz_name }}</h2>
<div class="col-md-5"></div>
</div>
<form action="{% url 'add_questions' %}" method="post">
{% csrf_token %}
{{ question_form.content }}
</form>
And finally the urls.py
urlpatterns = [
# ...
url(r'^add', views.add, name='add'),
url(r'^add_questions', views.add_questions, name='add_questions'),
]
The main problem is that after submitting the quiz name the system adds a quiz into database but doesn't redirect me to "add_questions". It still stays in "add" view.
Also I'm not sure if that's the proper way of how I should implement it. I've found many different ways of how I could do it but none of those worked out for me. (Solutions like HttpResponseRedirect(), redirect(), render() etc.)
To sum up:
1) What's the best way, in this case, to transfer data between views after submiting it in the form?
2) How should I redirect to another view so that it would display proper template?

As far as I know, this should work fine for you:
views.py:
#login_required
def add(request):
if request.method == 'POST':
QuizForm = QuizNameForm(request.POST)
if QuizForm.is_valid():
# create a quiz
new_quiz = QuizForm.save(commit=False)
new_quiz.owner = request.user
new_quiz.save()
print("Quiz saved, now I'm redirecting to add_questions")
return render(request, 'quiz/add_questions.html', {'quiz_id': new_quiz.id})
else:
QuizForm = QuizNameForm()
return render(request, 'quiz/add.html', {'quizForm': QuizForm})
#login_required
def add_questions(request):
quiz = DB_questions.objects.get(id=request['quiz_id'])
if request.method == 'POST':
print("ok, post method - cool")
#create a question/answer forms
else:
print("Got into else in add_questions view")
Question_form = QuestionForm()
return render(request, 'quiz/add_questions.html', {'quiz_name': quiz.name, 'question_form': Question_form })
This way you can both render your template and also send the quiz_id through the request object (not session).

Related

User can post comment only after login in Django

I'm coding a news website,I want the user can submit the comment of the news only after they have logged in,if not,the website will return to login.html.
Now I have made it that only the user who have logged in can submit a comment,the issue is once I log off and submit a comment the error says:
Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x10fed10b8>>": "NewsComments.user" must be a "UserProfile" instance.
Note:I have rewrote the User models and rename it UserProfile .It works very well.
Here is my news/views.py:
def newsDetailView(request, news_pk):
news = News.objects.get(id=news_pk)
title = news.title
author = news.author_name
add_time = news.add_time
content = news.content
category = news.category
tags = news.tag.annotate(news_count=Count('news'))
all_comments = NewsComments.objects.filter(news=news)
comment_form = CommentForm(request.POST or None)
if request.method == 'POST' and comment_form.is_valid():
comments = comment_form.cleaned_data.get("comment")
comment = NewsComments(user=request.user, comments=comments, news=news)
comment.save()
return render(request, "news_detail.html", {
'title': title,
'author': author,
'add_time': add_time,
'content': content,
'tags': tags,
'category': category,
'all_comments': all_comments,
'comment_form': comment_form
})
Here is my news.detail.html
<form method="POST" action="">{% csrf_token %}
<div class="form-group">
<label for="exampleFormControlTextarea1"><h5>评论 <i class="fa fa-comments"></i></h5></label>
<textarea id="js-pl-textarea" class="form-control" rows="4"
placeholder="我就想说..." name="comment"></textarea>
<div class="text-center mt-3">
<input type="submit" id='js-pl-submit' class="btn btn-danger comment-submit-button" value='Submit'>
</input>
</div>
</div>
</form>
Here is my urls.py:
path('-<int:news_pk>', newsDetailView, name="news_detail"),
You could use djangos login-required-decorator.
#login_required
def newsDetailView(request, news_pk):
...
EDIT to expand the idea from my comments.
You could have two views, one with the login_required decorator. (You could also use class-based-views (CBV) if you prefer)
def view_news_details(request, news_pk):
...
#login_required
def post_comments(request, news_pk):
...
Each view would have their own url:
url(r'^(?P<news_pk>[0-9]+)/$', views.view_news_details, name='view-details'),
url(r'^(?P<news_pk>[0-9]+)/comment/$', views.post_comments, name='comment'),
Then you can have only one template but with conditional rendering. This template will be rendered by the view views.view_news_details, but the form will send its data to the other view (note the forms action attribute).
... display the news details here ...
{% if request.user.is_authenticated %}
<form method="POST" action="{% url 'comment' news_instance.pk %}">
... here goes the content of the form ...
</form>
{% endif %}
Redirect the user to your login view before let him submit any data in your views.py :
# Codes here
if request.method == 'POST': # We separe those two "if statements", because
# We want to redirect the user to login even if the form is not valid, User can bypass your security concern
# For Django < 2.0, use it with () if request.user.is_authenticated():
if request.user.is_authenticated:
return redirect("login_url_name") # Or HttpResponseRedirect("login_url")
if comment_form.is_valid():
comments = comment_form.cleaned_data.get("comment")
# Rest of codes
Important
In your template, give access to the form to only authenticated users
{% if request.user.is_authenticated %}
<form method="POST" action="">{% csrf_token %}
<div class="form-group">
<label for="exampleFormControlTextarea1"><h5>评论 <i class="fa fa-comments"></i></h5></label>
<textarea id="js-pl-textarea" class="form-control" rows="4"
placeholder="我就想说..." name="comment"></textarea>
<div class="text-center mt-3">
<input type="submit" id='js-pl-submit' class="btn btn-danger comment-submit-button" value='Submit' />
</div>
</div>
</form>
{% endif %}
You can check whether the requested user is logged-in or not by user.is_authenticated() method, which returns a boolean value.
Try the following snippet,
def newsDetailView(request, news_pk):
# code
if request.method == 'POST' and comment_form.is_valid():
if not request.user.is_authenticated():
return HttpResponse("Please do login")
comments = comment_form.cleaned_data.get("comment")
comment = NewsComments(user=request.user, comments=comments, news=news)
comment.save()
return render(request, "news_detail.html", {
'title': title,
'author': author,
'add_time': add_time,
'content': content,
'tags': tags,
'category': category,
'all_comments': all_comments,
'comment_form': comment_form
})

Django form error:local variable 'context' referenced before assignment

I'm collecting data from form, processing data(right now I'm not) and displaying the result on the same HTML page from where the user submits the form.
Here is my views.py file:
def index(request):
template = 'predictor/index.html'
if request.method =='POST':
form = EvalForm(request.POST)
if form.is_valid():
text ='thank you for submitting form'
else:
text='something wrong.'
context: {
'text':text,
}
return render(request,template,context)
else:
form = EvalForm()
return render(request,template)
Here is my index.html file
<form method="POST" action="{% url 'predictor' %}">
{% csrf_token %}
//all input fields including submit button here
</form>
<div class="result">
{{ text }}
</div>
All other things like urls are configured properly.
What I'm doing wrong here?
You have typo in your code.
Should be context = {'text':text,} instead of context: {'text':text,}.

Django: Return to template along with template variables

I am using Django to build a student-teacher portal.
I have two groups of users - teachers and students. If user is a teacher, i need to provide a different template (a form for selecting student's registration number). I followed this link to do the same.
Here are the code snippets:
home.html
{% if is_teacher %}
<p style="color:blue; text-align:center; font-size:160%"><b>Course taken: <span style="color:green">IT000</span></b></p>
<form action="/" method="post" align="center">
{% csrf_token %}
<div align="center">{{ form }}</div>
<input type="submit" value="Get student's results!" class="btn btn-primary" style="margin-top:10px"/>
</form>
{% else %}
<p style="color:blue; text-align:center; font-size:160%"><b>Performance for the subject <span style="color:green">IT000</span> is shown below.</b></p>
{% endif %}
views.py
#login_required(login_url="login/")
def home(request):
is_teacher = request.user.groups.filter(name='teachers').exists()
if is_teacher:
if request.method == 'POST':
form = Regno(request.POST)
if form.is_valid():
selected_reg = Student.objects.filter(regno=request.POST.get('regno'))
return render(request, 'home.html',{'selected_reg': selected_reg,'form':form})
else:
form = Regno()
return render(request, 'home.html', {'form': form,'user':request.user,'is_teacher':is_teacher})
else:
selected_reg = Student.objects.filter(regno=request.user)
return render(request, 'home.html', {'user':request.user,'is_teacher':is_teacher,'selected_reg':selected_reg})
Here, Regno is a Form for the teacher to enter student's registration no.
When a teacher initially logs in, he is displayed the form. However, after he submits the form, it is not displaying the form. It executes the {% else %} part of the template. How do I make sure the is_teacher template variable is passed on to this template after the teacher submits the form?
I read about Django sessions but I'm not sure if it can help.
Instead of passing the is_teacher every time, you should use RequestContext to pass it along with your requests.
Looks like in your post call on form submit, you are not passing is_teacher variable. (in below code)
if form.is_valid():
selected_reg = Student.objects.filter(regno=request.POST.get('regno'))
return render(request, 'home.html',{'selected_reg': selected_reg,'form':form})
You should add is_teacher in above.
Also, better way to handle it via custom template tag -
register = template.Library()
#register.filter(name='has_group')
def has_group(user, group_name):
group = Group.objects.get(name=group_name)
return True if group in user.groups.all() else False
and in the template -
{% if request.user|has_group:'teachers' %}
....
{% else %}
....
{% endif %}
You can avoid passing it with each call. Hope it helps.

How to redirect to previous page in Django after POST request

I face a problem which I can't find a solution for. I have a button in navbar which is available on all pages and it is a button responsible for creating some content.
View that links with button:
def createadv(request):
uw = getuw(request.user.username)
if request.method =='POST':
form = AdverForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
return HttpResponseRedirect('/', {'username': request.user.username, 'uw': uw})
args = {}
args.update(csrf(request))
args['username'] = request.user.username
args['form'] = AdverForm()
args['uw'] = uw
return render_to_response('createadv.html', args)
If you can see now I always redirect to main page '/' after creating content but I want to go back to the page with which I launched the creation of content.
You can add a next field to your form, and set it to request.path. After you processed your form you can redirect to the value of this path.
template.html
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="hidden" name="next" value="{{ request.path }}">
<button type="submit">Let's Go</button>
</form>
views.py
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)
This is roughly what django.contrib.auth does for the login form if I remember well.
If you pass through an intermediate page, you can pass the 'next' value via the querystring:
some_page.html
Go to my form!
template.html
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="hidden" name="next" value="{{ request.GET.next }}">
<button type="submit">Let's Go</button>
</form>
You can use the HTTP_REFERER value:
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
Note that this will not work if the client disabled sending referrer information (for example, using a private/incognito browser Window). In such a case it will redirect to /.
You can use this
return redirect(request.META.get('HTTP_REFERER'))
Make sure to import this
from django.shortcuts import redirect
My favorite way to do that is giving the request.path as GET parameter to the form.
It will pass it when posting until you redirect.
In Class-Based-Views (FormView, UpdateView, DeleteView or CreateView) you can directly use it as success_url.
Somewhere i read that it's bad practise to mix GET and POST but the simplicity of this makes it to an exception for me.
Example urls.py:
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('user/update/', UserUpdateView.as_view(), name='user_update'),
]
Link to the form inside of the template:
Update User
Class-Based-View:
class UserUpdateView(UpdateView):
...
def get_success_url(self):
return self.request.GET.get('next', reverse('home'))
In your function based view you can use it as follows:
def createadv(request):
uw = getuw(request.user.username)
if request.method =='POST':
form = AdverForm(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
next = request.GET.get('next', reverse('home'))
return HttpResponseRedirect(next)
args = {}
args.update(csrf(request))
args['username'] = request.user.username
args['form'] = AdverForm()
args['uw'] = uw
return render_to_response('createadv.html', args)
you could do this easily with a simple one-liner JS
<button onclick="history.back()">Go Back</button>
This will take you back to the previous page of your history list.
If you don't have a history
https://www.w3schools.com/jsref/met_his_back.asp
Use HTTP_REFERER value:
for use in func return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
for use in template Go Back
In case this helps someone I got this to work in class based UpdateView
template
<form class="form" method="POST">
{% csrf_token %}
<!-- hidden form field -->
<input type="hidden" id="previous_page" name="previous_page"
value="/previous/page/url">
<!-- any other form fields -->
{{ form.name|as_crispy_field }}
{{ form.address|as_crispy_field }}
<!-- form submit button -->
<button class="btn btn-primary" type="submit" id="submit">Submit</button>
</form>
<!-- JS to insert previous page url in hidden input field -->
<script>
prev = document.getElementById("previous_page");
prev.value = document.referrer;
</script>
views.py
class ArticleUpdateView(generic.UpdateView):
model = Article
form_class = ArticleForm
template_name = 'repo/article_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
# if form is valid get url of previous page from hidden input field
# and assign to success url
self.success_url = self.request.POST.get('previous_page')
return super().form_valid(form)
The view now redirects you back to the page where you had clicked the "Update/Edit" button. Any URL query parameters are also preserved.

Django form fails to display

I have a simple Django form being passed through a view to a template where it should display, but, for a reason that I -- after 5 hours -- have failed to deduce, it does not. Any and all ideas welcome, I'm dying to solve this irksome problem.
I have the following Django form:
class BandAddToCartForm(forms.Form):
LENGTH_CHOICES = ( ('XS', 'XS'),
('S', 'S'),
('M', 'M') )
length = forms.Select(choices=LENGTH_CHOICES)
quantity = forms.IntegerField(widget=forms.HiddenInput())
band_sku = forms.CharField(widget=forms.HiddenInput())
# override the default __init__ so we can set the request
def __init__(self, request=None, *args, **kwargs):
self.request = request
super(BandAddToCartForm, self).__init__(*args, **kwargs)
# custom validation to check for cookies
def clean(self):
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError("Cookies must be enabled.")
return self.cleaned_data
It is passed to the template through the following view:
def show_custom_watches(request,
template_name="catalog/custom_watches.html"):
bands = Band.objects.all()
page_title = "Custom Watches"
meta_keywords = "custom, watches, beaded"
meta_description = "Custom beaded watches for every occassion."
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
# need to evaluate the HTTP method
if request.method == 'POST':
#add to cart, create bound form
postdata = request.POST.copy()
form = BandAddToCartForm(request, postdata)
#check if posted data is valid
if form.is_valid():
#add to cart and redirect to cart page
cart.add_band_to_cart(request)
# if test cookie worked, get rid of it
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
url = urlresolvers.reverse('show_cart')
return HttpResponseRedirect(url)
else:
# it's a GET, create the unbound form. Note request as a kwarg
band_form = BandAddToCartForm(request=request, label_suffix=':')
# set the test cookie on our first GET request
request.session.set_test_cookie()
return render_to_response("catalog/custom_watches.html",
locals(),
context_instance=RequestContext(request))
Lastly, here is the relevant bit of template where the form is failing to display:
{% for b in bands %}
<div class="watch_list_item">
<img class="" src="{{ MEDIA_URL }}images/bands/thumbnails/{{ b.image }}" alt="{{ b.name }}" />
<div class="watch_form_area">
<p>{{ b.name }}</p>
<form method="post" action="." class="cart">{% csrf_token %}
{{ band_form.as_p }}
<input type="submit" value="Add To Cart" name="add_product" alt="Add To Cart" class="add_to_cart_button" id="add_only_product" />
</form>
</div>
</div>
{% endfor %}
The Add to cart button appears as it should, but the length selector completely fails to display. Any ideas?
The first
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
always happens before you initialise the form, remove it and it should work.