validate and submit Django form (django-crispy-forms) with Ajax - python

I'm very new at django and web development. I need help submitting Django form (using django-crispy-forms) with Ajax How do I:
validate input
submit without reloading
display errors in case of validation failure
Right now i can submit the form and it saves the entries into the database but reloads the entire page in the process.
i have included relevant snippets of my code below
// forms.py
class SubscriptionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SubscriptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.template_pack = 'bootstrap3'
self.helper.form_tag = True
self.helper.form_id = 'sub-form-1'
self.helper.form_class = 'form-inline'
self.helper.field_template = 'bootstrap3/inline_field.html'
self.helper.layout = Layout(
Div(Div(css_class='sub-form-notifications-content'),
css_class='sub-form-notifications'),
InlineField('name', id='subName'),
InlineField('email', id='subEmail'),
FormActions(Submit('submit', 'Notify me', css_class='form-control')),
)
class Meta:
model = Sub
fields = "__all__"
def clean_email(self):
"""
Validate that the supplied email address is unique for the
site.
"""
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError(
_("This email address is already in use. Please supply a different email address."))
return self.cleaned_data['email']
// views.py
from django.shortcuts import render, redirect
from .forms import SubscriptionForm
from .models import Sub
def index(request):
if request.method == 'POST':
sub_form = SubscriptionForm(request.POST)
if sub_form.is_valid():
sub_form.save()
# return redirect('landing:landing')
else:
sub_form = SubscriptionForm()
return render(request, 'landing/index.html', {'sub-form': sub_form})
// template
...
{% crispy sub-form %}
...
// rendered form HTML
<form class="form-inline" id="sub-form-1" method="post">
<input type='hidden' name='csrfmiddlewaretoken'
value='tdiucOssKfKHaF7k9FwTbgr6hbi1TwIsJyaozhTHFTKeGlphtzUbYcqf4Qtcetre'/>
<div class="sub-form-notifications">
<div class="sub-form-notifications-content">
</div>
</div>
<div id="div_id_name" class="form-group">
<label for="subName" class="sr-only requiredField">Name</label>
<input type="text" name="name" maxlength="30" required placeholder="Name"
class="textinput textInput form-control" id="subName"/>
</div>
<div id="div_id_email" class="form-group"><label for="subEmail" class="sr-only requiredField">Email address</label>
<input type="email" name="email" maxlength="60" required placeholder="Email address"
class="emailinput form-control" id="subEmail"/>
</div>
<div class="form-group">
<div class="controls ">
<input type="submit" name="submit" value="Notify me" class="btn btn-primary" id="submit-id-sub-form"/>
</div>
</div>
</form>

I'll try to give you an idea of how to do it easily.
Add the onsubmit event listener to the form and error-block e. g. bellow the form where errors will be displayed.
template
<form class="form-inline" id="sub-form-1" method="post" onsubmit="sendData();">
...
</form>
<div class="error-block">
<!-- Here is the space for errors -->
</div>
now the handler which will send the data to the view to be validated and saved
<script>
function sendData(e) {
e.preventDefault(); // don not refresh the page
var form_data = {
name: $('input[name="name"]').val(),
... other field values ...
}
$.ajax({
url: "{% url 'url-you-want-send-form-to' %}",
method: "POST",
data: form_data,
success: function(response) {
// here are the success data in the response
// you can redirect the user or anything else
//window.location.replace("{% url 'success-url' %}");
},
error: function(response) {
// here are the errors which you can append to .error-block
//$('.error-block').html(response);
}
})
}
</script>
In the view you'll receive the data in the same form as the form would be submitted but you will not have to render whole template to the response, but only the erros from the validated form, so the view you will send your ajax POST request to will have to be different from the view which renders the form. You can create another which will handle it.

Related

Django Form, form is not being submitted

I am pretty new to django and I am having some issues with my form. It is not submitting anything. I don´t have any idea why, no issue appears in the terminal. It displays the form correctly, but when filling it out and submitting, it just redirects me to the same form but blank. I check the database and nothing´s been added. My code below:
#views.py
def ContractView(request):
form=contractsform(request.POST)
if form.is_valid():
con =form.save()
return redirect("{% url 'contracts' %}", con.id)
else:
form = contractsform()
return render(request, 'contform.html', {'form': form})
#contform.html
<div class="card-body">
<form action="" method="POST" class="row g-3">
{% csrf_token %}
<label for="{{ form.subject.id_for_label }}">Name:</label>
{{form.name}}
<div class="col-md-6">
<label for="{{ form.subject.id_for_label }}">Contractor:</label>
<div class="input-group input-group-sm mb-3">
{{form.contractor}}
<button id="new-vendor" class="btn btn-outline-secondary" type="button">+</button>
</div>
</div>
<div class="col-md-6">
<label for="{{ form.subject.id_for_label }}">Contractee:</label>
{{form.contractee}}
</div>
...
<div class="col-md-6">
<button type="button" onclick="javascript:history.back()">Cancel</button>
</div>
<div class="col-md-6">
<input type="submit" value="Submit" class="btn btn-primary" style="float: right;">
</div>
</form>
#forms.py
class contractsform(forms.ModelForm):
class Meta:
model = Contratos
fields = '__all__'
widgets = {
'name': forms.TextInput(attrs ={'class': 'form-control'}),
'contractee': forms.TextInput(attrs={'class': 'form-control'}),
'contractor': forms.Select(attrs={'class': 'form-control', 'id': 'contractor_view' }),}
#urls.py
urlpatterns = [
path('contracts/', views.contratostabla, name='contracts'),
path('contracts/add/', ContractView, name='new-contract'),
]
You are currently redefining the form as an empty form when it is not valid, you need to change you logic to use a form filled with POST data when the request method is POST and an empty form when the method is GET. You still need to use the filled form for POST when it's invalid so that you get the errors and the previous data
def ContractView(request):
if request.method == 'POST':
form = contractsform(request.POST)
if form.is_valid():
con = form.save()
return redirect("{% url 'contracts' %}", con.id)
else:
form = contractsform()
return render(request, 'contform.html', {'form': form})
FYI on readability, the convention is to use CamelCase for classes so your form would be named ContractsForm and lowercase-with-underscores for functions so your view would be contract_view
You can update your view like this
def ContractView(request):
form = contractsform(request.POST or None)
if form.is_valid():
con = form.save()
con.save()
return redirect("{% url 'contracts' %}", con.id)
else:
form = contractsform()
return render(request, 'contform.html', {'form': form})
I think this will solve your problem

Why is my django form sending as a GET and then giving a 405 error at POST

I am trying to create a 'change email' form in Django. The form is showing in the template but when I submit the form this happens in the terminal
[09/Feb/2016 09:19:55] "GET /change_email/ HTTP/1.1" 200 3775
[09/Feb/2016 09:19:55] "GET /static/css/styles.css HTTP/1.1" 304 0
[09/Feb/2016 09:20:09] "POST /account/ HTTP/1.1" 405 0
And it sends to a blank page. I've also looked in the view and noticed, with some print statements, that it's going to the last else statement rather than going through if request.POST:
Here's my code:
View:
def change_email(request):
if request.POST:
print "POST"
form = ChangeEmailForm(request.POST)
if form.is_valid():
email = request.POST['current_email']
email_new = request.POST['new_email']
email_confirmed = request.POST['confirm_email']
form.save()
if email_new == email_confirmed:
User.objects.get(email=email).update(email=request.POST['new_email'])
else:
return HttpResponse("These emails don't match")
else:
return render(request, 'registration/email_change_form.html', {'form': form})
else:
print "GET"
form = ChangeEmailForm()
return render(request, 'registration/email_change_form.html', {'form': form})
Form:
class ChangeEmailForm(forms.ModelForm):
current_email = forms.EmailField()
new_email = forms.EmailField()
confirm_email = forms.EmailField()
class Meta:
model = User
fields = ('current_email', 'new_email', 'confirm_email')
Template:
<form action="{% url 'account' %}" method="post">
{% csrf_token %}
<div class="form-group">
<label for="Current Email">Current Email:</label>
<div class="col-sm-10">
{% render_field form.current_email type="email" class+="form-control" placeholder="Current Email" %}
</div>
</div>
<div class="form-group">
<label for="New Email">New Email:</label>
<div class="col-sm-10">
{% render_field form.new_email type="email" class+="form-control" placeholder="New Email" %}
</div>
</div>
<div class="form-group">
<label for="Confirm New Email">Confirm New Email:</label>
<div class="col-sm-10">
{% render_field form.confirm_email type="email" class+="form-control" placeholder="Confirm New Email" %}
</div>
</div>
<div class="form-group" style="padding-top: 40px; text-align:right">
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
URLs:
urlpatterns = [
url(r'^change_email/$', views.change_email, name="change_email"),
url(r'^account/$', TemplateView.as_view(template_name='account_details.html'), name="account"),
]
form action=""
This will send the form back to the change_email and go through the POST method part (currently it sends to another url). Also, use
if request.method == 'POST':
TemplateView will not have post method defined. It is a basic class which makes template rendering easy.
Django will throw 405 METHOD NOT ALLOWED on class based views if appropriated method is not defined for corresponding HTTP verb.
You should implement your own class:
class AccountView(TemplateView):
template_name='account_details.html'
def post(self):
# your code to handle HTTP post
# return response
def get(self):
# your code to handle HTTP get
# return response
and in urlpatterns:
url(r'^account/$', AccountView.as_view()),
More information at : https://docs.djangoproject.com/es/1.9/topics/class-based-views/

Bootstrap modal for Flask login form in the base.html without sending the form in every view function?

I am pretty new to the Flask framework with Miguel Grinberg's Book, Flask Web Development as my reference. For now i have a view function that handles logins and I have no idea how to efficiently implement this using a modal activated by a login button located on the navbar(bootstrap) since this is handled at domain.com/login. The only solution i can think of as deleting this view and copying the code in every view function which i am sure is not a good solution at all.
from .forms import LoginForm
from flask.ext.login import login_user
#auth.route('/login/', methods = ['GET', 'POST'])
def login():
form=LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user, form.remember_me.data)
flash("login success")
return url_for(".index",username=user.username))
flash("Invalid username or password.")
return render_template('auth/login.html',form=form)
You can use a modal and to the action attribute of the form you pass the route to your handler function /login/. Something like so:
<div class="modal hide" id="myModal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">x</button>
<h3>Login to MyWebsite.com</h3>
</div>
<div class="modal-body">
<form method="post" action='/login/' name="login_form">
<p><input type="text" class="span3" name="eid" id="email" placeholder="Email"></p>
<p><input type="password" class="span3" name="passwd" placeholder="Password"></p>
<p><button type="submit" class="btn btn-primary">Sign in</button>
Forgot Password?
</p>
</form>
</div>
<div class="modal-footer">
New To MyWebsite.com?
Register
</div>
</div>
You could use ajax for this.
For example, your login button has class login-js.
Using jquery you could do something like this:
$('.login-js').click(function(){
$.ajax({
url: '/login/',
type: "POST",
dataType: 'json',
success: function(data) {
if (data.status == 'ok') { //if your response have 'status' key
alert('Logged');
} else {
alert('Error.');
}
}
});
})
But you'll need to change your view to return json response. I'm not confident how to do this using Flask.

AngularJS/Django & Forms server-side validation result

I have an html form that is generated automatically in Django, displayed as a modal window using Bootstrap UI and needs to be posted via ajax with Angular.
The problem is that some validations happen on the server-side and in case of an error I need to display any errors the same way I would if it were full http post (e.g. return the form with the invalid user input in red highlighting and errors next to each field).
I'm trying to avoid to write a custom response in my view and custom parsing in my script per case as I have quite a lot of forms that require validation and need to be submitted in a modal window.
Any help really appreciated, thanks!
Here is my code:
Form
from django import forms
from djangular.forms import NgFormValidationMixin
from djangular.styling.bootstrap3.forms import Bootstrap3Form
class FormBase(Bootstrap3Form, NgFormValidationMixin, forms.Form):
form_name = None
class AppRequestForm(FormBase):
form_name = 'app_request_form'
class_type = AppRequest
name = forms.RegexField(
r'^.*$',
max_length=100,
widget=forms.TextInput(attrs={'ng-model': 'request.name'}),
error_messages={'invalid': 'Please enter a valid name.'}
)
email = forms.RegexField(
r'^.*$',
label='Email',
max_length=100,
widget=forms.TextInput(attrs={'ng-model': 'request.email'}),
error_messages={'invalid': 'Please enter a valid email.'}
)
View
from django.views.generic import FormView, TemplateView
from public.forms import AppRegisterForm, AppRequestForm
class ViewBase(TemplateView):
def get_context_data(self, **kwargs):
kwargs.update(theme=THEME_PATH)
kwargs.update(page=self.request.resolver_match.url_name)
return super(ViewBase, self).get_context_data(**kwargs)
class FormViewBase(ViewBase, FormView):
def get_context_data(self, **kwargs):
kwargs.update(form=self.get_form())
return super(FormViewBase, self).get_context_data(**kwargs)
def form_valid(self, form):
self.success_action(form)
return super(FormViewBase, self).form_valid(form)
class AppRequest(FormViewBase):
template_name = 'public/app_request.html'
form_class = AppRequestForm
def success_action(self, form):
# persist data
Template:
<form name="{{ form.form_name }}" method="post" action="/app/request" class="form" validate>
<div class="modal-header">
<h3 class="modal-title">Request details</h3>
</div>
<div class="modal-body">
<div class="well">
<fieldset>
{% csrf_token %}
{{ form.as_div }}
</fieldset>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="submit(request)" ng-disabled="{{ form.form_name }}.$invalid">
Submit
</button>
<button type="button" class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</form>
Script:
$scope.apps.request = function (id) {
var url = '/app/request';
var modalInstance = $modal.open({
templateUrl: url,
controller: function ($scope, $http, $modalInstance, state) {
$scope.submit = function (request) {
$http({
method: 'POST',
url: state.url,
data: $.param(request),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).success(function (data, status, headers, config) {
$modalInstance.close();
}).error(function (data, status, headers, config) {
// Display validation errors on the modal window and allow user to re-submit
});
};
$scope.cancel = function () {
$modalInstance.dismiss();
};
},
resolve: {
state: function () {
return {
scope: $scope,
url: url,
id: id
};
}
}
});
};

405 error with AJAX for submission

I am trying to perform ajax for my form in my Django project. I am pretty new to webdev in general so what I think I am supposed to do is have an ajax call POST in a specific url which I chose as /message/. When a POST request is sent, my view should respond to this request and update my database and also render some text within /message/. However I am getting 405 error.
urls.py:
from home.views import HomeView, contact_request_view
urlpatterns = patterns('',
url(r'^$', HomeView.as_view(), name="home"),
url(r'^message/', contact_request_view)
)
views.py:
class HomeView(generic.TemplateView):
template_name = "home/home.html"
def contact_request_view(request):
if request.method == 'POST':
form = ContactForm()
if form.is_valid():
obj, created = User.objects.get_or_create(email=form.cleaned_data['email'],
first_name=form.cleaned_data['first_name'],
last_name=form.cleaned_data['last_name'])
ContactRequest.objects.create(user=obj,
message=form.cleaned_data['message'])
return render(request, "message", "testing")
return render(request, "message", "FAIL")
form.py:
class ContactForm(forms.Form):
first_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'class': 'form-control'}))
last_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'class': 'form-control'}))
email = forms.EmailField(required=True, widget=forms.EmailField())
message = forms.CharField(required=True, widget=forms.Textarea(attrs={'class': 'form-control contact-margin', 'rows': '8', 'placeholder': 'Message...'}))
JS:
var contactForm = document.getElementById("contact-form");
var firstName = contactForm.getElementById("firstname");
var lastName = contactForm.getElementById("lastname");
var email = contactForm.getElementById("email");
var message = contactForm.getElementById("message");
contactForm.submit(function() {
var contactInfo = {
first_name: firstName.val(),
last_name: lastName.val(),
email: email.val(),
message: message.val()
};
$.ajax({
type: 'POST',
url: "/message/",
data: contactInfo,
success: function() {
console.log("posted");
},
error: function() {
console.log("failed")
}
});
return false;
});
form:
<section id="contact">
<div class="container">
<div class="title-container">Contact Us</div>
<div class="title-caption">Reach us at (415)-911-9999</div>
<form class="contact-input" id="contact-form" method="post">
{% csrf_token %}
<div class="col-md-12">
<div class="col-md-6">
<div class="contact-input-margin form-group">
<input id="firstname" class="form-control" placeholder="First name">
</div>
<div class="contact-input-margin form-group">
<input id="lastname" class="form-control" placeholder="Last name">
</div>
<div class="contact-input-margin form-group">
<input id="email" class="form-control" placeholder="Email">
</div>
<div class="contact-input-margin form-group">
<input class="form-control" placeholder="Phone number">
</div>
</div>
<div class="contact-input-margin col-md-6">
<div class="form-group">
<textarea id="message" rows="8" class="form-control contact-margin" placeholder="Message...">
</textarea>
</div>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-xl">
</form>
</div>
</section>
Method Not Allowed because you have not defined the POST in your view, the error make sence. Your view needs the POST method to be declared:
class HomeView(generic.TemplateView):
def post(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
You should send the csrf token with the post request in your JS code or you have to turn off the csrf protection on your message view with csrf_exempt() method like this url(r'^message/', csrf_exempt(contact_request_view)).
You would need to create a form instance and populate it with data from the request:
def contact_request_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# if form is valid, you can process the data in form.cleaned_data
...
return render(request, "message", "testing")
else:
do_some_stuff() # if you `print(form.errors)` you can see what cause the errors
return render(request, "message", "FAIL")
Also, you might want to take a look at ModelForm
, so you can redeclare your ContactForm like this:
class ContactForm(ModelForm):
class meta:
model = User
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
After that, if form is valid, you can call form.save(), and django will create the User instance automatically for you.

Categories