new to Django and I'm having a little trouble with this.
I have a registration form that creates rows in a database on successful submission.
Upon submission, I would like to redirect the user to a login form (which requires me to pass in the form as a parameter for render()), passing a status of 'REGISTRATION_SUCCESSFUL' as a parameter as well.
return render(request, 'login.html', {
'form': login_form(),
'status': 'REGISTRATION_SUCCESSFUL'
})
Based on the status, my template then displays a message to indicate the successful registration.
All works fine until you try to refresh the login page, it tries to submit the registration form again (I suppose because the request was forwarded to the login page).
Everything I've tried involves forwarding the request, which is proving to be a problem. Help?
Thanks in advance!
After form submission instead of using render, use HTTPResponseRedirect. Always return an HttpResponseRedirect after successfully dealing with POST data. This prevents data from being posted twice if a user hits the Back button.
The problem that you face here is that HTTPResponseRedirect does not allow you to pass the context of the status being REGISTRATION_SUCCESSFUL.
Based on the status, my template then displays a message to indicate the successful registration.
This is where you could use Django's messages framework. Right before the HTTPResponseRedirect, you could do something like:
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import RegistrationForm
def registration(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
# ... do something special here if needed
messages.add_message(request, messages.INFO, 'Registration was successful!')
return HttpResponseRedirect(reverse('login_page'))
else:
form = RegistrationForm()
return render(request, 'registration.html', {'form': form})
Now in your "login_page", you can show the messages. FYI - the messages are on a per-session basis, so the "Registration was successful" message will only be displayed to the user you redirected:
login_page.html: (example from the messages documentation)
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }} </li>
{% endfor %}
</ul>
{% endif %}
<form method="post">{% csrf_token %}
{{ form }} {# Your login form #}
</form>
In this example, any messages you added will be displayed above your login form. If there are other messages such as "invalid registration," they will be displayed.
Please be sure to Enable the Django Messages Framework by adding the following to your settings.INSTALLED_APPS:
INSTALLED_APPS = (
...
'django.contrib.messages',
)
And your settings.MIDDLEWARE_CLASSES must contain:
MIDDLEWARE_CLASSES = (
...
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
And your settings.TEMPLATE_CONTEXT_PROCESSORS must contain:
TEMPLATE_CONTEXT_PROCESSORS = (
...
'django.contrib.messages.context_processors.messages',
)
After registration is successful, you might have to consider redirect which is a common practice after a successful post is done.
Django provides a short-cut for redirects.
return redirect('some-view-name', foo='bar')
return redirect('/some/url/')
For your case you can consider:
kwargs={'status': 'REGISTRATION_SUCCESSFUL' }
return redirect('login-view', **kwargs)
from django.contrib import messages
from django.http import HttpResponseRedirect
if form1.is_valid():
registration = form1.save(commit=False)
registration.save()
messages.success(request, 'Your message here.')
return HttpResponseRedirect('/login/')
in templates.
in div where you want to display the message
{% block message %}
{% include "includes/messages.html"%}
{% endblock %}
Then create the message.html file in include folder
<div id="messages">
{% for message in messages %}
<div class="alert alert-{{message.tags }} alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
{{ message }}
</div>
{% endfor %}
</div>
Done so your page will redirect to login page and on that page message will get display.
Related
Running Django 4.1.1. Having this code below in template. By clicking a button it sends a data to delete apropriate marker.
<form method="POST">
{% csrf_token %}
<ol>
{% for marker in markers %}
<li>
{{ marker }} - <button class="button btn-primary" id="delete" value="{{ marker.pk }}" type="submit">Delete</button>
</li>
{% endfor %}
</ol>
</form>
In views.py
def user_markers(request):
markers = Marker.objects.filter(owner_id=request.user.id).select_related()
if request.method == "POST":
print(request.POST.get("delete")) # gives me None
marker = Marker.objects.get(pk=request.POST.get("delete"))
marker.delete()
context = {
"markers": markers,
}
return render(request, "hub/markers.html", context)
The problem is that request.POST.get("delete") is empty.
POST data has only 'csrfmiddlewaretoken'
Do I miss something?
You need to specify this as name, so:
<button class="button btn-primary" id="delete" name="delete" value="{{ marker.pk }}" type="submit">Delete</button>
If you do not specify a name, it will not be include in the POST data, and thus it will not contain any value that is passed with the button.
You might want to improve the view function:
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect
#login_required
def user_markers(request):
markers = Marker.objects.filter(owner_id=request.user.id).select_related()
if request.method == 'POST':
get_object_or_404(
Marker, owner=request.user, pk=request.POST.get('delete')
).delete()
return redirect('name-of-some-view') # need to redirect
context = {
'markers': markers,
}
return render(request, 'hub/markers.html', context)
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
#Contact.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class='row'>
<div class ='col-md-4 col-md-offset-4'>
<h1> {{title}} </h1>
{% if confirm_message %}
<p>{{ confirm_message }}</p>
{% endif %}
{% if form %}
<form method='POST' action=''>
{% csrf_token %}
{{ form.errors }}
{{ form.non_field_errors }}
{% crispy form %}
<input type='submit' value='submit form' class='btn btn-default' />
</form>
{% endif %}
</div>
</div>
{% endblock %}
# froms.py
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Field
from crispy_forms.bootstrap import (PrependedText, PrependedAppendedText, FormActions)
class contactForm(forms.Form):
name = forms.CharField(required = False , max_length =100, help_text="100 characters max ")
email= forms.EmailField(required = True)
comment = forms.CharField(required =True, widget=forms.Textarea)
Server Logs
System check identified no issues (0 silenced).
September 13, 2017 - 07:38:19
Django version 1.11.5, using settings 'app3.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
GET
hello from not valid
[13/Sep/2017 07:38:23] "GET /contact/ HTTP/1.1" 200 5413
[13/Sep/2017 07:42:20] "GET / HTTP/1.1" 200 4356
[13/Sep/2017 07:42:27] "GET /about/ HTTP/1.1" 200 3985
GET
hello from not valid
[13/Sep/2017 07:42:37] "GET /contact/ HTTP/1.1" 200 5413
The request never becomes post. When I hit submit on the form it
never shows up as post request. What could possibly I be doing wrong ?
#Views page
from django.shortcuts import render
from .forms import contactForm
from django.conf import settings
from django.core.mail import send_mail
def contact(request):
form = contactForm()
confirm_message = None
title = 'Contact'
context ={'title' : title, 'form' : form }
print request.method
# print form
# if request.method=='GET':
# form = contactForm()
if request.method =='POST':
form = contactForm(request.POST )
print "hello from not valid "
if form.is_valid():
print "hello"
name = form.cleaned_data['name']
comment=form.cleaned_data['comment']
emailFrom=form.cleaned_data['email']
subject ='Message from mysite.com'
message='%s %s' %(comment, name )
emailTo=[settings.EMAIL_HOST_USER]
title = 'Thank you'
confirm_message="Thank you, we ll get back to you "
context ={'title' : title,'confirm_message' :
confirm_message}
template ='contact.html'
return render(request , template , context)
This is my views page handling all the business logic for the application
When I run this application, the code never reaches the request==post block. I am unable to figure out why ? I pasted contact.html and forms.py for more visibility.
EDIT:
I have implemented all the changes suggested but the form never renders the post method. I could say something wrong with form but I don't know what.
UPDATE2:
The issue has been resolved and the problem seems to crispy forms. I read the documentation and couldn't find anything to pin point the error besides the fact that it was rendering the request as post. Decided to remove it and now it works perfectly fine.
Thank you all for your help and suggestions.
You can see "hello from not valid" string in your server log that means your POST request is successfully sended to server.
However, second if statement checks if form is valid and this is the line where things get south. Since you do not have else case for not valid form, you cannot see the right error message.
Fix your form and cover the not valid case.
In your template your form is constructed wrongly.
If you use {% crispy %} tag in your template, it makes a form.
If you don't want form tags included, set the form_tag attribute for your form helper to False.
self.helper.form_tag = False
You need not explicitly use {% csrftoken %} tag, crispy adds that for you.
Also I don't see that you're using crispy form helper in your forms.py.
I need one help. I need to implement the forget password functionality using Django. I am using the Django signup and login page. My code is below:
login.html:
{% extends 'base.html' %}
{% block content %}
<h2>Log in</h2>
{% if form.errors %}
<p style="color: red">Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }}<br>
{{ field }}<br>
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
{% if field.help_text %}
<p><small style="color: grey">{{ field.help_text }}</small></p>
{% endif %}
</p>
{% endfor %}
<button type="submit">Log in</button>
New to My Site? Sign up
</form>
{% endblock %}
views.py:
class Signup(View):
""" this class is used for user signup """
def get(self, request):
""" this function used to get the sign up form """
form = UserCreationForm()
return render(request, 'plant/signup.html', {'form': form})
def post(self, request):
""" this function used for post the sign up data """
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
return redirect('login')
class AuthLogin(View):
""" Its for login """
def get(self, request):
""" this function used to get the login form """
form = AuthenticationForm()
return render(request, 'plant/login.html', {'form': form})
def post(self, request):
""" this function used for post the login data """
form = AuthenticationForm(None, request.POST or None)
if form.is_valid():
login(request, form.get_user())
return redirect('/')
urls.py:
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name="plant/index.html")),
url(r'^logout/$', auth_views.logout,
{'next_page': 'login'}, name='logout'),
url(r'^login/$', core_views.AuthLogin.as_view(), name='login'),
url(r'^signup/$', core_views.Signup.as_view(), name='signup'),
]
In login page I should have the forget password link. When user will click on it, the reset password page will open and another one condition is after trying 3 wrong attempt the forget password button will be invisible for 1 hour.
Do not create this functionality yourself but use the built-in Django auth views. https://docs.djangoproject.com/en/1.11/topics/auth/default/#built-in-auth-views
The only thing you need to do is add the contrib auth urls to your project:
urlpatterns = [
url('^', include('django.contrib.auth.urls')),
]
This gives you all views like login, logout, password reset etc.
If you want to customise the templates, copy the templates form /path/to/site-packages/django/contrib/admin/templates/registration/ to your app project/app/templates/registration/ and make any customisations
there.
If your app is listed before 'django.contrib.auth' in INSTALLED_APPS (it should always be like that) your custom templates will be picked first.
I'm reading the Djangobook and I'm on ch 7.There is actually a line that says "#todo - explain CSRF token"
When I was following the examples (I'm pretty sure I've followed them exactly), I cannot get the code to function properly.
Here is my template
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contact/" method="post">
{% csrf_token %}
<p>Subject: <input type="text" name="subject"></p>
<p>Your e-mail (optional): <input type="text" name="email"></p>
<p>Message: <textarea name="message" rows="10" cols="50"></textarea></p>
<input type="submit" value="Submit">
</form>
</body>
</html>
Here is my view
from django.core.mail import send_mail
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template import RequestContext
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '#' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply#example.com'),
['siteowner#example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
return render(request, 'contact_form.html',
{'errors': errors}, context_instance=RequestContext(request))
This is the error I'm getting
Forbidden (403)
CSRF verification failed. Request aborted.
Help
Reason given for failure:
CSRF token missing or incorrect.
In general, this can occur when there is a genuine Cross Site Request Forgery, or when Django's CSRF mechanism has not been used correctly. For POST forms, you need to ensure:
- Your browser is accepting cookies.
- The view function uses RequestContext for the template, instead of Context.
- In the template, there is a {% csrf_token %} template tag inside each POST form that targets an internal URL.
- If you are not using CsrfViewMiddleware, then you must use csrf_protect on any views that use the csrf_token template tag, as well as those that accept the POST data.
You're seeing the help section of this page because you have DEBUG = True in your Django settings file. Change that to False, and only the initial error message will be displayed.
You can customize this page using the CSRF_FAILURE_VIEW setting.
EDIT******
I discovered that I can view the source code for my form and the csrf_token isn't being inserted even though I have it in my template. I looked up common solutions. Some people suggested I do this
return render_to_response('contact_form.html',
{'errors': errors}, context_instance=RequestContext(request))
But this doesn't work for me either.
I just checked my settings.py and I see 2 middlewares added not just CsrfViewMiddleware -
MIDDLEWARE_CLASSES = (
...
‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.middleware.csrf.CsrfResponseMiddleware’,
)
Try adding more.
I have a view that validates and saves a form. After the form is saved, I'd like redirect back to a list_object view with a success message "form for customer xyz was successfully updated..."
HttpResponseRedirect doesn't seem like it would work, because it only has an argument for the url, no way to pass dictionary with it.
I've tried modifying my wrapper for object_list to take a dict as a parameter that has the necessary context. I the return a call to this wrapper from inside the view that saves the form. However, when the page is rendered, the url is '/customer_form/' rather than '/list_customers/'. I tried modifying the request object, before passing it to the object_list wrapper, but that did not work.
Thanks.
request.user.message_set was deprecated in Django 1.2 and has been removed since Django 1.4, the message framework should be used instead.
from django.contrib import messages
# messages.add_message(request, level, message, extra_tags='', fail_silently=False)
messages.add_message(request, messages.INFO, "Your Message")
Alternatively, you can use one of the shortcut functions:
from django.contrib import messages
messages.debug(request, "Your Message")
messages.info(request, "Your Message")
messages.success(request, "Your Message")
messages.warning(request, "Your Message")
messages.error(request, "Your Message")
Messages can then be rendered on the template with:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
Please note the answer suggested here is only applicable to Django < 1.2:
Do you have control over the view that you are redirecting to? In that case you can save the context in the session before redirecting. The target view can pick up the context (and delete it) from the session and use it to render the template.
If your only requirement is to display a message then there is a better way to do this. Your first view can create a message for the current using auth and have the second view read and delete it. Something like this:
def save_form(request, *args, **kwargs):
# all goes well
message = _("form for customer xyz was successfully updated...")
request.user.message_set.create(message = message)
return redirect('list_view')
def list_view(request, *args, **kwargs):
# Render page
# Template for list_view:
{% for message in messages %}
...
{% endfor %}
Messages are saved to the database. This means that you can access them even after a redirect. They are automatically read and deleted on rendering the template. You will have to use RequestContext for this to work.
For Django => 1.2 read the answer involving messages
To expand on Antoine's helpful answer: if you want to process the messages in your views module, rather than the template:
from django.contrib.messages import get_messages
def my_view(request):
# Process your form data from the POST, or whatever you need to do
# Add the messages, as mentioned above
messages.add_message(request, messages.INFO, form.cleaned_data['name'])
return HttpResponseRedirect('/other_view_url/')
def other_view(request):
storage = get_messages(request)
name = None
for message in storage:
name = message
break
return render(request, 'general/other_view.html', {'name': name})
I found the following to work if more than just a message needs to be added to the redirect:
from django.shortcuts import redirect
import urllib
def my_view(request):
...
context = {'foo1': bar1, 'foo2': bar2, 'foo3': bar3}
return redirect('/redirect_link/?' + urllib.parse.urlencode(context))
See also
how to pass context data with django redirect function?
In Django 2.x + you can simply use messages framework that comes with Django
views.py
from django.contrib import messages
def register(request):
....
messages.success(request,"You have registered successfully, now login!")
return redirect('login-page')
And in you, login.html template do add this code
{% if messages %}
{% for message in messages %}
<div class="alert alert-success alert-dismissible fade show">
<strong>Success!</strong> {{message}}
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
{% endfor %}
{% endif %}
Note this example can be applied to anywhere you want to display a message for success
If you want to pass an error message simply use messages.error(request, "Error message")
To Django Admin, I could redirect with the several types of messages as shown below:
# "views.py"
from django.contrib import messages # Here
from django.shortcuts import redirect
def my_view(request):
messages.debug(request, 'This is debug')
messages.info(request, 'This is info')
messages.success(request, 'This is success')
messages.warning(request, 'This is warning')
messages.error(request, 'This is error')
return redirect("http://localhost:8000/admin/store/order/")
But, I don't know why only "debug" message is not displayed even though "DEBUG = True" in "settings.py":