Populate form field with AJAX query - javascript

I would like to populate a django form field each time a dropdown value is selected inside a specific field.
Example :
I have a list of businesses (business A, business B, ...) and a list of countries. Each business is located in a specific country.
Business A --> France
Business B --> Germany
Business C --> England
In my form, when I select a specific business in my dropdown list, I would like to populate immediatly the country field with the associated country. If the business change, the associated country too.
I'm using Django 1.11.18
The context :
In my code, MemberState corresponds to the Country as my example above and RBI corresponds to the business.
My Model :
class MemberState(models.Model):
name = models.CharField(max_length=256, verbose_name=_('Name'))
code = models.CharField(max_length=256, verbose_name=_('Code'))
class RBI(models.Model):
short_name = models.CharField(max_length=256, verbose_name=_('Short name'), unique=True)
member_state = models.ForeignKey(MemberState, verbose_name=_('Member State'))
...
My Form :
class FinalProductSearchForm(forms.Form):
releasing_body = ShortNameModelChoiceField(queryset=RBI.objects.filter(active=True).order_by('short_name'), required=False,
widget=forms.Select(), empty_label=_('Select'), label=_('Releasing Body/Institution'))
member_state = forms.ModelChoiceField(queryset=MemberState.objects.filter(active=True).order_by('name'), required=False,
widget=forms.Select(), empty_label=_('Select'), label=_('Member state'))
...
I would like to select a releasing_body in my form and prefill the member_state field associated. Each time I change the realeasing_body it loads the associated member_state.
I tried some things in Django but I need AJAX request. Unfortunatly, I have never done this kind of things.
My work with AJAX part :
So, this is my first try (which doesn't work) :
I created this function in my views.py file :
def ajax_member_state_request(request):
if request.is_ajax():
release_body = request.GET.get('releasing_body', None)
print(release_body)
member_state_ini = ReleaseBodyInstitution.objects.values_list('member_state', flat=True).get(id=release_body)
print(member_state_ini)
member_state = MemberState.objects.get(id=member_state_ini)
print(member_state)
return JsonResponse({'member_state': member_state})
In my urls.py file, I added :
url(r'^finalproduct/list/$', FinalProductListView.as_view(),
name='finalproduct-list'),
url(r'^finalproduct/list/ajax_member_state_request/$', views.ajax_member_state_request, name='ajax_member_state_request'),
And finally in my HTML file :
<form id="providerForm" data-provider-url="{% url 'ajax_member_state_request' %}" class="navbar-search" method="GET" action="{{ url }}">
{% csrf_token %}
<div class="row">
<div class="col-md-5">
{{ search_form.releasing_body|as_crispy_field }}
</div>
<div class="col-md-5">
{{ search_form.member_state|as_crispy_field }}
</div>
</div>
<input type="submit" class="btn btn-default" value="{% trans 'Search' %}" />
<input type="button" class="btn btn-default" name="clear" value="Reset" onclick="clearForm(this.form);">
</form>
The AJAX part looks like this :
$("#id_releasing_body").change(function () {
var url = $("#providerForm").attr("data-provider-url");
var releasingBodyId = $(this).val();
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
data: {
'releasing_body': releasingBodyId
},
success: function (data) {
$("#id_member_state").val(data.member_state);
}
});
});

I would implement a view that given a business name returns a JsonResponse with the country (following your example).
With that in place in the success section of the ajax request set the value of the country form field.
The view:
def contry_for_bussines(request):
if request.is_ajax():
member_state = ReleaseBodyInstitution.objects.get(id=release_body).member_state
return JsonResponse({'member_state': member_state})
In the ajax
$("#id_releasing_body").change(function () {
var url = $("#providerForm").attr("data-provider-url");
var releasingBodyId = $(this).val();
$.get(url, {'releasing_body': releasingBodyId}, function(data){
$("#id_member_state").text(data.member_state);
});
});

Check this approach if it helps, I followed those steps for my project and successfully populated choicefields with AJAX request. The only problem is the form is not binding when submitted despite a value is selected in all fields (working on that now)
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html

Related

Use AJAX to update Django Model

I am trying to understand how to update a Django model using AJAX without loading a new page or explicitly having the user press a save button. I have found many tutorials that deal with getting results from Django models using AJAX but I haven't found any that deal with updating models using AJAX.
Here is what I have so far
I have the following Django model:
#models.py
class Upload(models.Model):
email = models.EmailField()
title = models.CharField(max_length=100)
language = models.ForeignKey('about.channel', on_delete=models.CASCADE, default='Other')
date = models.DateField(auto_now_add=True)
file = models.FileField()
completed = models.BooleanField(default=False)
I am accepting those uploads through a form, all is well. I am then displaying those on a page through the following view:
#views.py
def submissions(request):
context = {
'uploads': Upload.objects.all().order_by('-completed', '-date')
}
return render(request, 'content/submissions.html', context)
The template for this page:
#submissions.html
<div class="row row-cols-1 row-cols-md-3">
{% for upload in uploads %}
<div class="col mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title"> {{ upload.title }} </h5>
<h6 class="card-subtitle"> {{upload.language}} </h6>
<a href="{{ upload.file.url }}" class="channel-link card-link" download> Download </a>
{% if upload.completed %}
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" data-id="{{ upload.id }}" checked>
<label class="form-check-label" for="{{ upload.id }}"> Completed </label>
</div>
{% else %}
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" data-id="{{ upload.id }}">
<label class="form-check-label" for="{{ upload.id }}"> Completed </label>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
Here is a portion of the page:
The checkboxes work, and returns the appropriate checked vs unchecked on the completed model field.
I am trying to understand how to connect this to AJAX in order to be able to update the completed model field from just clicking the checkbox located on each card on the page without having to load a new page/model or press a save button. I have tried the following view for updating the model but no luck:
#views.py
def completed(request, *args, **kwargs):
upload = Upload.objects.get(id=id)
upload.completed = not upload.complete
upload.save()
return JsonResponse({'status': 200})
And the jQuery/AJAX:
$('.form-check').on('click', '.form-check-input', function() {
var dataID = $(this).data('id');
$.ajax({
type: 'POST',
url: 'content/completed/',
data: {
id: dataID
},
success: function () {
console.log('Success')
}
})
});
Alas, I get nothing. I'm sure I have things wrong in both my view I'm using to update and the AJAX call, but I'm at a loss for what to do next.
I am assuming your form is firing off your AJAX call successfully with the correct data. Maybe you aren't accessing your AJAX post parameter correctly
Your api:
def completed(request)
id = request.POST.get('id') # Add this line
upload = Upload.objects.get(id=id)
upload.completed = not upload.complete
upload.save()
return JsonResponse({'status': 200})
If this doesn't solve it, let me know what messages you are getting from the backend, and what sort of data you sent in your Ajax call. If you use FireFox, press ctrl+shift+I and click on Network tab to see what data you sent from your Ajax call.
In regard to your comment about CSRF, try putting this code (csrfSafeMethod() and $.ajaxSetup()) before you call your $.ajax()
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
};
...
$('.form-check').on('click', '.form-check-input', function() {
var dataID = $(this).data('id');
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", Cookies.get('csrftoken'));
}
}
});
$.ajax({
url: "content/completed/",
type: "POST",
data: {
id: dataID
},
success: function () {
console.log('Success')
}
})
});
This was how I handled CSRF for my APIs without putting them into #csrf_exempt, maybe it will work for you too

How to keep a form from submitting when creating a Django model object with many-to-many field selection that is doesn't exist in the related model?

I'm using Django to create a web app. I have multiple Django models with relationships between each other. One of my models is called Type and another is called TestEquipment. The Type model has a many-to-many relationship with TestEquipment.
To allow the user to create a new Type, I have an html form and to select which TestEquipment will be associated with that Type I have am using searchable dropdown with "chips" (javascript) which loads all TestEquipment and allows you to search and select multiple objects to add. When the user submits the form, the selected TestEquipment is added.
Everything works great other than when the user enters text and presses enter, instead of selecting from the dropdown, a chip is added with that text. When the form submits it tries to add a TestEquipment that doesn't exists.
I would like to find a way to either not allow adding an objects that doesn't exist or throw an alert "must select from existing test equipment"; somehow I have to ensure that the form does not submit to my constructor and create the new Type if text is added to the field.
I've tried to find an answer to this and absolutely have had no luck. Any help is REALLY appreciated!
Django Models code:
class TestEquipment(models.Model):
name = models.CharField(max_length=64, unique=True)
notes = models.TextField(null=True, blank=True)
def __str__(self):
return f"{self.name}"
class Type(models.Model):
name = models.CharField(max_length=64, unique=True)
type_folder = models.URLField(null = True, blank = True)
type_test_equipment = models.ManyToManyField(TestEquipment, blank=True, related_name="type_test_equipment")
type_notes = models.TextField(null=True, blank=True)
test_sheet = models.URLField(null=True, blank=True)
type_test_guide = models.URLField(max_length=300, null=True, blank=True)
objects = TypeManager()
def __str__(self):
return f"{self.name}"
Views.py code:
def create_type_view(request):
if not request.user.is_authenticated:
return render(request, "jobs/login.html", {"message": None})
test_equipments = TestEquipment.objects.all()
equipment_types = Type.objects.all()
#pass in existing types of equipment
context= {
"equipment_types": equipment_types,
"test_equipments": test_equipments
}
if request.user.is_authenticated:
return render(request, "jobs/create_type.html", context)
create_type.html code (pulls in materialize styling and javascript):
<div>
<form id="type_form" class="col s12" action="{% url 'create_type' %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col s12">
<h6 style="color:#808080">Select Type-Specific Test Equipment</h6>
<div class="row">
<div class="input-field col s12">
<div id="test_equipment-chips" class="chips chips-autocomplete">
</div>
<input id="test_equipment" name="test_equipment" style="visibility: hidden;" value="">
</div>
</div>
</div>
</div>
<div class="row">
<input id="submit-btn" type="submit" value="Confirm Add" onclick="onSubmit();" />
</div>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script>
function onSubmit() {
var chipInstance = M.Chips.getInstance($("#test_equipment-chips"));
var value = Array.from(chipInstance.chipsData.map(x=>x.tag)).join(",");
$("#test_equipment").val(value);
}
$('#test_equipment-chips').chips({
autocompleteOptions: {
data: {
{% for test_equipment in test_equipments %}
'{{test_equipment}}': null,
{% endfor %}
},
limit: Infinity,
minLength: 0,
}
});
</script>
code for constructor:
def create_type(request):
#extract type name from form
type_name = request.POST["type_name"]
#create new type object
new_type=Type.objects.create_type(type_name)
#fill in all properties that were submitted in the form
new_type.type_folder = request.POST["type_folder"]
new_type.test_sheet = request.POST["test_sheet"]
new_type.type_test_guide = request.POST["type_test_guide"]
new_type.type_notes = request.POST["type_notes"]
equipment_list=request.POST["test_equipment"].split(",")
for equipment_name in equipment_list:
equipment=TestEquipment.objects.get(name=equipment_name)
new_type.type_test_equipment.add(equipment.pk)
#save to database
new_type.save()
return HttpResponseRedirect(reverse("jobs"))
So the most important thing to do there is to perform a check in your python because you shouldn't perform a get request to the database like that unless you know it can't fail. As you're seeing, it just ends up with 500 errors when objects don't exist.
So job 1 would be to do this in your view;
for equipment_name in equipment_list:
try:
equipment=TestEquipment.objects.get(name=equipment_name)
except TestEquipment.DoesNotExist:
# Log the error somehow if you like?
print(f"Equipment not found with the name {equipment_name}")
else:
# This only executes when the exception wasn't raised
new_type.type_test_equipment.add(equipment.pk)
Using the above code, it'd just ignore invalid input, which suits a minimum viable option. If you wanted to provide an error in the event of invalid input however you should run through all values in equipment_list to ensure they exist before adding them to the relationship. That way you could render the form again with the errors attached.
I've had a look at the demo/docs for chips and it doesn't look like it can help prevent invalid inputs, but you could do some validation in javascript yourself to prevent submission.
You should also have a look at using django's forms because your view is directly accessing post data. By using forms you can handle validation logic outside of your view & there's a bunch more benefits from them. If you're yet to find out about forms, give this page a read; https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms

How can a Ajax/jQuery script show 2+ dependent dropdown box entries within one Form url?

I am developing a simple form prototype that contains 4 entries in PythonAnywhere (Python 3.7 + Django):
PinID (Independent, simple manual number entry)
Region (Independent Dropdown Box)
Name (Region-Dependent Dropdown Box)
Source (Name-Dependent Dropdown Box)
What shows up in the Name box is dependent on the Region box, and what shows up in the Source box is dependent on the Name box. So ultimately, the Source Box is dependent on the Region box (If A, then B. If B, then C. So C is dependent on A). To note, if the Region or Name box is blank, their respective dependents are blank.
As my current code is written (which is likely wrong), I can only get my Name box to autopopulate correctly. The Source box remains blank, but it does indeed autopopulate correctly after I refresh the page. However, I intend to keep the form all in one url. I refer to two other .html files to "insert" themselves into the form's .html file without refreshing the page. In the jQuery script, I put the identical segment of code for the second dependent dropdown box under the success function of the first dependent dropdown box, which might be my issue.
So, I presume my coding issue is initiating two different url references within one jQuery script. Does anyone know if I can correct my jQuery script to reference both urls? I included my relevant Django files for reference and if I am overlooking an error in those particular files.
For reference, I have based my work using this tutorial. However, this tutorial only worked with one dependent dropdown box.
form.html (Ajax/jQuery)
<form method="post" id="nameForm" data-names-url="{% url 'ajax_load_names' %}" data-sources-url="{% url 'ajax_load_sources' %}" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Add</button>
Nevermind
</form>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$("#id_region").change(function () {
var url = $("#nameForm").attr("data-names-url");
var regionId = $(this).val();
$.ajax({
url: url,
data: {
'region': regionId
},
success: function (data) {
$("#id_name").html(data);
$("#id_source").change(function () {
var url = $("#nameForm").attr("data-sources-url");
var nameId = $(this).val();
$.ajax({
url: url,
data: {
'name': nameId
},
success: function (data) {
$("#id_source").html(data);
}
});
});
}
});
});
</script>
name_dropdown_list_options.html
<option value="">---------</option>
{% for name in names %}
<option value="{{ name.pk }}">{{ name.name }}</option>
{% endfor %}
source_dropdown_list_options.html
<option value="">---------</option>
{% for source in sources %}
<option value="{{ source.pk }}">{{ source.source }}</option>
{% endfor %}
forms.py
class RequestForm(forms.ModelForm):
class Meta:
model = Request
fields = ('pinid', 'region', 'name', 'source')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].queryset = Name.objects.none()
self.fields['source'].queryset = Source.objects.none()
if 'region' in self.data:
try:
region_id = int(self.data.get('region'))
self.fields['name'].queryset = Name.objects.filter(region_id=region_id).order_by('region')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty Name queryset
elif self.instance.pk:
self.fields['name'].queryset = self.instance.region.name_set.order_by('region')
if 'name' in self.data:
try:
name_id = int(self.data.get('name'))
self.fields['source'].queryset = Source.objects.filter(name_id=name_id).order_by('name')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty Source queryset
elif self.instance.pk:
self.fields['source'].queryset = self.instance.name.source_set.order_by('source')
models.py
class Region(models.Model):
region = models.CharField(max_length=30)
def __str__(self):
return self.region
class Name(models.Model):
region = models.ForeignKey(Region, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Source(models.Model):
name = models.ForeignKey(Name, on_delete=models.CASCADE)
source = models.CharField(max_length=30)
def __str__(self):
return self.source
class Request(models.Model):
pinid = models.PositiveIntegerField()
region = models.ForeignKey(Region, on_delete=models.SET_NULL, null=True)
name = models.ForeignKey(Name, on_delete=models.SET_NULL, null=True)
source = models.ForeignKey(Source, on_delete=models.SET_NULL, null=True)
def __int__(self):
return self.pinid
views.py
class NameCreateView(CreateView):
model = Request
form_class = RequestForm
success_url = reverse_lazy('name_changelist')
def load_names(request):
region_id = request.GET.get('region')
names = Name.objects.filter(region_id=region_id).order_by('region')
return render(request, '/home/name_dropdown_list_options.html', {'names': names})
def load_sources(request):
name_id = request.GET.get('name')
sources = Source.objects.filter(name_id=name_id).order_by('name')
return render(request, '/home/source_dropdown_list_options.html', {'sources': sources})
First move change your form.html javascript, so that the $("#id_source").change(function ()
is on the same level as the name change function. I suppose otherwise it will never be called:
forms.html javascript
<script>
$("#id_region").change(function () {
var url = $("#nameForm").attr("data-names-url");
var regionId = $(this).val();
$.ajax({
url: url,
data: {
'region': regionId
},
success: function (data) {
$("#id_name").html(data);
}
});
});
$("#id_source").change(function () {
var url = $("#nameForm").attr("data-sources-url");
var nameId = $(this).val();
$.ajax({
url: url,
data: {
'name': nameId
},
success: function (data) {
$("#id_source").html(data);
}
});
});
</script>
I would start with a much simpler design to fetch the names and sources. Remove the complete init of your request form so that the standard init gives you all values for the names and sources. You will need them because the form validates against these values and if its empty none of them will be valid. You can remove the values then with a little javascript:
$(document).ready(function() {
$("#id_region").html("---"); // or whatever your null value will be
$("#id_source").html("---");
});
To get the values of names and sources you need to view functions:
def load_names(request, region_id):
names = Name.objects.filter(region__id=region_id).order_by('region')
html = ''
for name in names:
html += '<option value="{id}">{name}</option>\n'.format(id=name.id, name=name.name)
return HttpResponse(html)
def load_sources(request, name_id):
sources = Source.objects.filter(name__id=name_id).order_by('region')
html = ''
for source in sources:
html += '<option value="{id}">{source}</option>\n'.format(id=name.id, source=source.source)
return HttpResponse(html)
If this works fine you can change them to templatetags, if you want
At last you need the proper url definitions:
path('load_names', views.load_names, name='load-names'),
path('load_sources', views.load_sources, name='load-sources'),
Pass these to your view form.

Handle form submission in bootstrap modal with ajax and class based views

i'm quite new using django and i've been stuck in this problem for several days.
I have a form.Form in a bootstrap modal on my template with only 1 field (email_field) and basically i need to submit that form via ajax, check if that email address is registered in the database, then send an invitation to that email and close the modal. if the email is not registered show the form errors without closing the modal. I've tried with different examples but can find the solution either because the examples don't handle errors or the form is not inside a modal or not using class based views
.
I'm having 2 issues with my code:
Not sure what to return in my view if the form is valid or invalid and how to handle errors in my js code to show them on the modal.(return tha form to render the errors or a JSON response??).
After the first success submission is made the form cannot be used again.(The size of the submit button changes and if you click it return a error : CSRF token missing or incorrect)
Form.py
class CollaboratorForm(forms.Form):
email_address = forms.EmailField(required=True,widget=forms.TextInput(attrs={'class': 'form-control focus-text-box', 'type': 'email',
'placeholder': 'Enter email'}))
def clean_email_address(self):
email = self.cleaned_data['email_address']
if not User.objects.filter(email=email):
raise forms.ValidationError('This user is not registered')
return email
def sendEmail(self, datas):
message = "Hello, " + datas['user_name']+" "+ datas['email_from'] + " invited you to collaborate in an existing project. Follow this link if you are interested " + datas['invitation_link']
msg = EmailMessage('Invitation from ' + datas['user_name'],
message, to=[datas['email_to']])
msg.send()
Template.html (project_detail.html)
<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>
<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
<div class="modal-body">
<form action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
{% csrf_token %}
<div class="form-group">
{% if collaborator_form.errors %}
<ol>
{% for error in collaborator_form.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
<label class="control-label">Invite someone by email</label>
<div class="input-group mt10">
{{ collaborator_form }}
<span class="input-group-btn">
<input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
</span>
</div>
</div>
</form>
</div>
</div>
Url.py
urlpatterns = [
url(r'^(?P<project_id>[0-9]+)/invite_collaborator$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]
View.py
class ProjectDetail(DetailView):
model = Project
template_name = 'experiments/project_detail.html'
pk_url_kwarg = 'project_id'
def get_context_data(self, **kwargs):
context = super(ProjectDetail, self).get_context_data()
project = get_object_or_404(Project,pk=self.kwargs["project_id"])
context["project"] = project
context["collaborator_form"] = CollaboratorForm()
return context
class InviteCollaborator(FormView):
form_class = CollaboratorForm
template_name = 'experiments/project_detail.html'
def post(self, request, *args, **kwargs):
collaborator_form = CollaboratorForm(data=request.POST)
project_id = request.POST['project_id']
current_project = Project.objects.get(id=project_id)
datas={}
if collaborator_form.is_valid():
cleaned_data = collaborator_form.cleaned_data
email_address = cleaned_data.get('email_address')
user = User.objects.get(pk=request.user.id)
invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
datas['user_name'] = user.first_name + ' ' + user.last_name
datas['email_from'] = user.email
datas['email_to'] = email_address
datas['invitation_link'] = invitation_link
collaborator_form.sendEmail(datas)
data = simplejson.dumps("Success")
return HttpResponse(data, content_type='application/json')
else:
return super(InviteCollaborator, self).form_invalid(collaborator_form)
invite_collaborator.js
function invite(project_id) {
$('#collaborator-form').submit(function(e) {
e.preventDefault();
$.ajax({
data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
type: $(this).attr('method'),
url: $(this).attr('action'),
});
$('#collaboratorModal').modal('toggle');
$('#collaboratorModal').on('hidden.bs.modal', function () {
$(this).find("input,textarea,select").val('').end();
});
});
};
I've read about using success: & error: on the js file but don't know how to use it without the appropriate "return" in the view
You need to have two ajax methods, one to get the form (as raw html) and one to post the form. You will have a corresponding get and post method in your view too.
get function of your view class:
def get(self, request, *args, **kwargs):
form = CollaboratorForm()
return render(request,'template.html',{'form':form})
def post(self, request, *args, **kwargs):
form = CollaboratorForm(request.POST)
if form.is_valid():
//save form
//return whatever you want to show on successful form submission
else:
//return bound form as html with errors
return render(request,'template.html',{'form':form})
js functions
have two seperate ajax function one for get (showing form) one for post(submitting form)
If you want to use templates on server's side, with FormView and ajax, I would suggest splitting templates into two parts - wrapper and form, load only wrapper via TemplateView, then fetch form with ajax. That allows you to send form with ajax and put responses (like form with errors) in wrapper.
Change your HTML template - take modal body's to another file, ex.:
project_detail.html
<script src="{% static '/experiments/js/invite_collaborator.js' %}"></script>
<div class="bootstrap-modal modal fade in" id="collaboratorModal" style="display: none;">
<div class="modal-body" id="collaboratorModalContent">
</div>
</div>
project_detail_content.html
<form action="{% url 'experiments:invite-collaborator' project_id=project.id %}" method="post" id=collaborator-form >
{% csrf_token %}
<div class="form-group">
{% if collaborator_form.errors %}
<ol>
{% for error in collaborator_form.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
<label class="control-label">Invite someone by email</label>
<div class="input-group mt10">
{{ collaborator_form }}
<span class="input-group-btn">
<input name="collaborator-commit" onClick="invite({{project.id}});" class="btn btn-primary" data-disable-with="Send Invitation" id="invite-button" type="submit">
</span>
</div>
</div>
</form>
FormView should handle GET and POST - first one to get the form in project_detail_content.html into modal, second for sending email. Fortunately, FormView can do all that for us! (I don't know from where you get that project variable though)
View.py
class InviteCollaborator(FormView):
form_class = CollaboratorForm
template_name = 'experiments/project_detail_content.html'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
project_id = self.request.POST['project_id']
current_project = Project.objects.get(id=project_id)
datas={}
cleaned_data = form.cleaned_data
email_address = cleaned_data.get('email_address')
user = User.objects.get(pk=request.user.id)
invitation_link = "http://exp.innovationhackinglab.com/projects/"+ str(current_project.id) + "/join/" + current_project.invitation_key
datas['user_name'] = user.first_name + ' ' + user.last_name
datas['email_from'] = user.email
datas['email_to'] = email_address
datas['invitation_link'] = invitation_link
form.sendEmail(datas)
data = simplejson.dumps("Success")
return HttpResponse(data, content_type='application/json')
Note few things - we use FormView, so for GET request it will return content of project_detail_content.html with CollaboratorForm, and on POST, same template with form and errors if form is invalid, or JSON with Success message otherwise.
What happened to project_detail.html? We will use TemplateView to create thw wrapper:
Url.py
urlpatterns = [
url(r'^invite_collaborator$', TemplateView.as_view(template_name="project_detail.html")),
url(r'^(?P<project_id>[0-9]+)/invite_collaborator/form$', views.InviteCollaborator.as_view(), name='invite-collaborator'),
]
Finally, JS
invite_collaborator.js
// In JS you need to make sure you fetch form from /project_id/invite_collaborator/form each time you show modal
$(document).ready(function(e) {
$('#collaboratorModalContent').load('invite_collaborator');
});
// Then, on submit we simply send data and handle response with success and error.
// With our current View, invalid form will generate successful response with form and error, so we need to check
function invite(project_id) {
$('#collaborator-form').submit(function(e) {
e.preventDefault();
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: $(this).serialize()+'&'+$.param({ 'project_id': project_id }),
success: function ( response, status, xhr, dataType ) {
if( dataType === 'json' ){
//Make sure response is 'Success' and close modal
$('#collaboratorModal').modal('toggle');
$('#collaboratorModal').on('hidden.bs.modal', function () {
$(this).find("input,textarea,select").val('').end();
});
});
};
}
else {
// It's not JSON, it must be HttpResposne with forms and errors, so it goes into modal's body
$('#collaboratorModalContent').html(response)
}
}
});
I still don't know where and how you get/set you project variable, so maybe TemplateView is bad choice...

AJAX post failing XMLHttpRequest check in Django view

I have a simple html page which renders with a number of nearly identical forms for the user to submit. Upon submit, the view is intended to add a row to the database, recreate the list of forms with slightly updated data, and send it back to the browser ('/route/complete/' maps to add_completed_route_view in urls.py).
This works perfectly the first time. Once the page has been redrawn with the new list of forms, however, the next submit will fail the request.is_ajax() test I have in the view. That causes it to skip to request.REQUEST['next'] and subsequently to home_view.
I've commented it out below, but I've also tried appending c['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' to the view but it hasn't helped.
I'm looking for help in ensuring that the headers continue to have the appropriate XMLHttpRequest param while the user submits through AJAX. Code is below, and help is much appreciated.
script.js
<script>
$(document).ready(function() {
$(".doneForm").submit(function() {
var route_id = $(this).find('input[name=route_id]').val()
var next = $(this).find('input[name=next]').val()
var reqData = {route_id:route_id,next:next}
$.ajax({
type: "post",
url: "/route/complete/",
data: reqData,
success: function(data) {
$("#routeTable").html(data);
}
});
return false;
});
});
</script>
and
template.html
<div id="routeTable">
{% for route in route_list %}
<div id="routeDone">
<form class="doneForm" action="/route/complete/" method="post">
<input type="hidden" name="route_id" value="{{ route.route_id }}" />
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
<input type="submit" value="Done" class="doneButton" />
</form>
</div>
{% endfor %}
</div>
and
views.py
def add_completed_route_view(request):
if request.method == 'POST' and request.user.is_authenticated():
add_completed_route(request)
if request.is_ajax():
wall_slug = get_wall_slug_from_route_id(request.REQUEST['route_id'])
c = get_context_for_wall_page(request, wall_slug)
# c['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
return render_to_response('m/js_route_table.html', c, context_instance=RequestContext(request))
else:
return HttpResponseRedirect(request.REQUEST['next'])
else:
return HttpResponseRedirect(reverse('home_view'))
The problem is that once the Ajax is completed, it replaces the original form with a new one - and this one no longer has the javascript event handler attached, so the next time the form submits via the normal POST method.
Luckily, jQuery has a couple of methods that handle this for you - live and delegate. So instead of $(".doneForm").submit(function() ..., do this:
$(".doneForm").live("submit", function() {...

Categories