Django Contact Form With Google Recaptcha v3 - javascript

I want to implement Google Recaptcha v3 into a working Django contact form.
I tried following these tutorials:
https://simpleisbetterthancomplex.com/tutorial/2017/02/21/how-to-add-recaptcha-to-django-site.html
https://foxrow.com/using-recaptcha-v3-with-django
https://medium.com/#mihfazhillah/how-to-implement-google-recaptcha-v3-on-your-django-app-3e4cc5b65013
But have had no luck trying to figure it out. Can someone please help me out?
Settings.py:
GOOGLE_RECAPTCHA_SECRET_KEY = '<KEY>'
Html:
<script>
grecaptcha.ready(function() {
grecaptcha.execute('<SITE_KEY>', {action: 'contact'})
.then(function(token) {
document.getElementById("form").appendChild(document.CreateElement(`<input type="hidden" name="g-recaptcha-response" value=${token}`);
});
});
</script>
<form role="form" action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<script src='https://www.google.com/recaptcha/api.js?render=<SITE_KEY>'></script>
<div class="g-recaptcha" data-sitekey="<SITE_KEY>"></div>
<button type="submit">Submit</button>
</form>
Views.py:
def contact(request):
form_class = ContactForm
# new logic!
if request.method == 'POST':
form = form_class(data=request.POST)
if form.is_valid():
contact_name = request.POST.get(
'contact_name'
, '')
contact_email = request.POST.get(
'contact_email'
, '')
form_content = request.POST.get('content', '')
# Email the profile with the
# contact information
template = get_template('contact_template.txt')
context = {
'contact_name': contact_name,
'contact_email': contact_email,
'form_content': form_content,
}
content = template.render(context)
email = EmailMessage(
"New contact form submission",
content,
"Your website" +'',
['GMAIL_ADDRESS'],
headers = {'Reply-To': contact_email }
)
email.send()
messages.info(request, "Success")
return render(request, 'contact.html', {
'form': form_class,
})
Forms.py:
from django import forms
class ContactForm(forms.Form):
contact_name = forms.CharField(required=True)
contact_email = forms.EmailField(required=True)
content = forms.CharField(
required=True,
widget=forms.Textarea
)
# the new bit we're adding
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['contact_name'].label = "Name:"
self.fields['contact_email'].label = "Email:"
self.fields['content'].label = "Message:"

I encountered the same problem today.
It seems the javascript snippet is incorrect:
Your element needs an id. (Mine is "cform" now.)
CreateElement does not exist, the "c" is lower case.
I could not create an element with attributes, I had to do it in several steps.
I don't usually code in javascript so I don't know the best practices, but here is what worked for me:
<script src='https://www.google.com/recaptcha/api.js?render=<KEY>'></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute(<KEY>, {action: 'contact'})
.then(function(token) {
ginput = document.createElement('input');
ginput.type = "hidden";
ginput.name = "g-recaptcha-response";
ginput.value = token;
document.getElementById("cform").appendChild(ginput);
});
});
</script>

Related

django: How to write JavaScript fetch for url with slug parameter?

A django and async newbie here, trying to improve a simple message-board app. I'm sure you've all seen this problem dozens of times, but I'm unable to find a solution...
Currently, when a user likes a posted message, it refreshes the whole page. I'd like to use simple JavaScript with the fetch API to prevent this, without having to resort to Ajax, as I've never used it. The problem is, I'm very new to the fetch method as well and I'm struggling to find the correct syntax for the url in the fetch request, as it uses the post model's slug field as a parameter. Like so:
urls.py
urlpatterns = [
...
path('post/<slug:slug>/', views.FullPost.as_view(), name='boards_post'),
path('like/<slug:slug>/', views.PostLike.as_view(), name='post_like'),
...
]
models.py
...
class Post(models.Model):
"""
Model for message posts
"""
STATUS = ((0, "Draft"), (1, "Published"))
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="board_posts"
)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
default="",
related_name="category_posts"
)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
content = models.TextField()
post_image = CloudinaryField('image', default='placeholder')
status = models.IntegerField(choices=STATUS, default=0)
likes = models.ManyToManyField(User, related_name="post_likes")
class Meta:
# Orders posts in descending order
ordering = ['-created_on']
def __str__(self):
return self.title
def number_of_likes(self):
return self.likes.count()
def get_absolute_url(self):
"""
Method to tell django how to find the url to any specific
instance of a post when the Post model is instantiated,
(i.e. a new post created). Returns the url generated by the
'boards_post' path from the FullPost class view, with
this model's slug field as a keyword argument. This
effectively acts as a redirect to the full_post.html template.
"""
return reverse('boards_post', kwargs={'slug': self.slug})
...
views.py
...
class FullPost(View):
"""
View for a single post, selected by the user, displaying
comments and likes. The url for each individual post is derived
from the Post model's slug field which is, in turn,
populated by the title.
"""
def get(self, request, slug, *args, **kwargs):
"""
Method to get post object.
"""
queryset = Post.objects.filter(status=1)
post = get_object_or_404(queryset, slug=slug)
comments = post.comments.order_by('created_on')
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
return render(
request,
"full_post.html",
{
"post": post,
"comments": comments,
"liked": liked,
"comment_form": CommentForm()
},
)
def post(self, request, slug, *args, **kwargs):
"""
Post method for comment form.
"""
queryset = Post.objects.filter(status=1)
post = get_object_or_404(queryset, slug=slug)
comments = post.comments.order_by("-created_on")
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
comment_form.instance.name = self.request.user
comment = comment_form.save(commit=False)
comment.post = post
comment.save()
else:
comment_form = CommentForm()
return redirect(self.request.path_info)
...
...
class PostLike(View):
"""
View for liking and unliking posts.
"""
def post(self, request, slug):
"""
Method to toggle liked/unliked state on a particular post.
"""
post = get_object_or_404(Post, slug=slug)
liked = True
if post.likes.filter(id=self.request.user.id).exists():
post.likes.remove(request.user)
liked = False
else:
post.likes.add(request.user)
likes = post.number_of_likes()
# ----- The original return statement is the one commented out below:
# return HttpResponseRedirect(reverse('boards_post', args=[slug]))
return JsonResponse({"likes": likes, "liked": liked})
...
Snippet from full post template
...
<div class="row">
<div class="col-1">
{% if user.is_authenticated %}
<strong>
<form class="d-inline" action="{% url 'post_like' post.slug %}" method="POST">
{% csrf_token %}
{% if liked %}
<!-- I used 'post.id' for the argument passed to the like function here, as I couldn't get 'post.slug' to work -->
<button class="btn-like" type="submit" name="post_id" value="{{ post.slug }}" onclick="like({{ post.id }})">
<i id="like-btn" class="fas fa-thumbs-up"></i>
</button>
<span id="likes-count">{{ post.number_of_likes }}</span>
{% else %}
<button class="btn-like" type="submit" name="post_id" value="{{ post.slug }}" onclick="like({{ post.id }})">
<i id="like-btn" class="far fa-thumbs-up"></i>
</button>
<span id="likes-count">{{ post.number_of_likes }}</span>
{% endif %}
</form>
</strong>
{% else %}
<strong class="text-secondary"><i class="far fa-thumbs-up"></i> <span id="likes-count">{{ post.number_of_likes }}</span></strong>
{% endif %}
</div>
...
All I've got so far in JavaScript is the following...
function like(post_id) {
let likeButton = document.getElementById("like-btn");
let likeCount = document.getElementById("likes-count");
console.log(likeCount.innerText);
console.log(post_id);
// -------- I've no idea what works here! I've tried both of the below
// -------- and several variations.
// -------- Obviously, none work.
// const url = "{% url 'post_like' post.slug %}";
//or const url = `/like/${post.slug}`;
fetch(url, {method: "POST"})
.then(response => response.json())
.then(data => {
console.log(data);
// ------ If I ever get the json data, I'll use it here to manipulate the
// ------ likeButton and likeCount variables in the DOM. Something like:
likeCount.innerHTML = data["likes"];
if (data["liked"] === true) {
likeButton.className = "fas fa-thumbs-up";
} else {
likeButton.className = "far fa-thumbs-up";
}
})
.catch((e) => alert("Unable to like/unlike post."));
// -------- or something.
}
Also, I know I need to handle the csrf token somehow but I've no idea how. Like I said, total newbie.
So, can anyone help? And does anyone have any advice?
Cheers!
For csrf_token problem add to view this decorator:
from django.utils.decorators import method_decorator
#method_decorator(csrf_exempt, name='dispatch')
class PostLike(View):
...
For fetch you need to have whole path, not just relative path. This should work:
url = "{{ request.scheme }}://{{ request.get_host }}{% url 'post_like' post.slug %}"

Django save a series of question and its corresponding answer set to database

I am a newbie in Django and I am working on a quiz functionality.
I give the user an interface to create a quiz. It is based on a multi-step form. I input a certain number of questions and hit next. It displays that amount of fields for me to set the questions. Each question has 4 answers and I use radio buttons to select the right answer. The problem is I don't know how to retrieve them in my views when I hit the submit button. Like I want each question and its corresponding answers to be properly stored in the respective models. Can you guys please? It's lengthy but easy to grasp and some details can be overlooked as they are just fields :)
Here is my code:
models.py
class PsychometricTest(models.Model):
DIFF_CHOICES = (
('easy', 'easy'),
('medium', 'medium'),
('hard', 'hard'),
)
internship = models.ForeignKey(Internship, on_delete=models.CASCADE)
name = models.CharField(max_length=120)
description = models.TextField()
number_of_questions = models.IntegerField()
time = models.IntegerField(help_text="Duration of the quiz in minutes")
required_score_to_pass = models.IntegerField(help_text="required score in %")
difficulty = models.CharField(max_length=6, choices=DIFF_CHOICES)
created = models.DateTimeField(auto_now_add=True)
scheduled_date_time = models.DateTimeField()
def __str__(self):
return self.name+" - "+self.topic
def get_questions(self):
return self.question_set.all()
class Question(models.Model):
text = models.CharField(max_length=200)
psychometric_test = models.ForeignKey(PsychometricTest, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.text
def get_answers(self):
return self.answer_set.all()
class Answer(models.Model):
test = models.CharField(max_length=200)
correct = models.BooleanField(default=False)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "Question: "+self.question.text+", answer: "+self.text+", correct: "+self.correct
class Result(models.Model):
psychometric_test = models.ForeignKey(PsychometricTest, on_delete=models.CASCADE)
student = models.ForeignKey(StudentUser, on_delete=models.CASCADE)
score = models.FloatField()
def __str__(self):
return self.student.user.username+": "+self.psychometric_test+", score="+self.score
add_test.html
<form style="margin-top: 20px;" action="{% url 'add_test' internship.id %}"
id="add_test_form" method="POST" >
<!--This form will contain the quiz information-->
{% csrf_token %}
<div class="step_div first" style="display: none;">
<label>Number of questions</label>
<input type="number" class="form-control" name="test_num_questions"
id="num_qu" min="1" oninput="validity.valid||(value='')" >
<script>
$('#num_qu').on('change', function(){
var num_of_qu = $('#num_qu').val();
var html = "";
for (var i = 1; i <= num_of_qu; i++) {
html += `<div class="row"><div class="form-row"><div class="form-group col-md-6">
<label>Name of question</label><input type="text" class="form-control"
name="question_text" placeholder="Question"></div></div></div>`;
for (j = 1; j < 5; j++) {
//4 answers
html += `<div class="row">
<div class="form-row">
<div class="form-group col-md-4">
<input type="text" class="form-control" name="answers_${j}" id="answer_${j}"
placeholder="Answer"><input
type="radio"
name="answer_for_qu${i}"
id="answer_${j}">
</div>
</div>
</div>`
}
}
$(".step_div.second").html(html);
});
views.py
def add_test(request, pid):
if not request.user.is_authenticated:
messages.warning(request, "Please login first")
return redirect('login')
if request.method == 'POST':
test_name = request.POST['test_name']
test_description = request.POST['test_desc']
test_num_questions = request.POST['test_num_questions']
test_duration = request.POST['test_duration']
test_threshold = request.POST['test_threshold']
test_diff_level = request.POST['test_diff_level']
test_datetime = request.POST['test_datetime']
# print(test_name)
# print(test_description)
# print(test_num_questions)
# print(test_duration)
# print(test_threshold)
# print(test_diff_level)
# print(test_datetime)
internship = Internship.objects.get(id=pid)
context = {
'internship': internship,
}
return render(request, 'add_test.html', context)
Second edit:
class SetData(APIView):
def post(self, request):
q_num = request.GET.get('q_num', False)
for i in range(int(q_num)):
question=Question(text=request.POST['question_text_{}'.format(i)])
question.save()
for j in range(4)
Answer(
text=request.POST['answers_{}_{}].format(i, j),
question=question,
...).save()
this is how you pass question total number via your request:
<form action="{% url 'add_test' internship.id %}?p_num=set this with js" method="POST" >
to set q_num with js, you need to select your "form element" and edit it's action. something like:
<form action="{% url 'add_test' internship.id %}" method="POST" >
...
document.getElementById("add_test_form").action += '?q_num=' + num_of_qu;
you won't need to change your url.
First edit:
I can't help you with javascript but here's how you get data from a post requst in django:
(This is a class based view, not much different from function based.)
class SetData(APIView):
def post(self, request):
test_num_questions =
request.POST['test_num_questions']
# or if for example you wanna check if a submit input
# (name='mysubmit') is clicked (in this case, you're
# not sure if request.POST['test_num_questions']
# returns True or error:
mysubmit =
request.POST.get('mysubmit', False)
# returns False if it there's no
# request.POST['mysubmit']
Btw, why don't you use django forms or even better, django model forms?

Django and jQuery: jQuery not working, but receiving no errors

I cannot figure out why my jQuery script isn't working. I've read through a bunch of stackOverflows but haven't been able to fix this issue.
I have a form, and when a checkbox is checked, I want to enable a form field with the id div_id_newLockName. If the checkbox is unchecked, I want the form field to be disabled and greyed out. The checkbox has id div_id_newNameBool I have written code that I think should work, but it doesn't. I'm not receiving any errors though, which makes troubleshooting difficult. I'm hoping a second set of eyes can help. A picture of what the form looks like can be found here: In this picture, the "change name?" checkbox corresponds with div_id_newNameBool, and the "Lock Name" char field is what I want to toggle enable or disable.
base.html (contains jQuery script)
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<style>
<!-- unimportant css stuff -->
</style>
<body class="w3-content" style="max-width:1200px">
<!-- !PAGE CONTENT! -->
{% block content %}{% endblock %}
<!-- End page content -->
</div>
<script>
$(document).ready(function(){
if ($("#div_id_newNameBool").is(':checked')){
$("#div_id_newLockName").prop("disabled", false);
}
else {
$("#div_id_newLockName").prop("disabled", true);
alert("DISABLED");
}
});
</script>
</body>
</html>
HTML page for form
{% extends "homepg/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<!-- Top header -->
<header>
</header>
<form method="POST">
<div class="w3-display-container w3-container">
{% csrf_token %}
<fieldset class="form-group" style="font-size:20px">
<legend class="border-bottom mb-4" style="font-size:20px, p">Edit Lock Information </legend>
{{ form|crispy }}
</fieldset>
<button type="submit" style="width: 200px; padding: 10px; left: 50%; margin:10px;">Change Lock</button>
</div>
</form>
{% endblock content %}
models.py
class Lock(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
lockName = models.CharField(max_length=20)
roomLoc = models.ForeignKey(Room, on_delete=models.CASCADE, null=True, blank=True)
state = models.BooleanField(default=0)
code1 = models.PositiveIntegerField(default=0, validators=[MinValueValidator(1000), MaxValueValidator(9999)])
code2 = models.PositiveIntegerField(default=0, validators=[MinValueValidator(1000), MaxValueValidator(9999)])
code3 = models.PositiveIntegerField(default=0, validators=[MinValueValidator(1000), MaxValueValidator(9999)])
code4 = models.PositiveIntegerField(default=0, validators=[MinValueValidator(1000), MaxValueValidator(9999)])
# create a dunder method to control how we want this to printed out
def __str__(self):
return self.lockName
forms.py
class changeLockForm(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(changeLockForm, self).__init__(*args, **kwargs)
self.fields['roomLoc'] = forms.ModelChoiceField(queryset=Models.Room.objects.filter(owner=None) | Models.Room.objects.filter(owner=user), label='Room Location')
self.fields['lockName'] = forms.ModelChoiceField(queryset=Models.Lock.objects.filter(owner=None) | Models.Lock.objects.filter(owner=user), label="Current Lock Name")
newNameBool = forms.BooleanField(label='Change Name? Y/N')
newLockName = forms.CharField(min_length=2, max_length=20, label='Lock Name', disabled=True)
#roomLoc = forms.ModelChoiceField(queryset=Models.Room.objects.filter(user=request.user), label='Room Location')
state = forms.BooleanField(required=False, label='LOCKED/UNLOCKED')
code1 = forms.IntegerField(initial=1000, min_value=1000, max_value=9999, label='First Code')
code2 = forms.IntegerField(initial=1000, min_value=1000, max_value=9999, label='Second Code')
code3 = forms.IntegerField(initial=1000, min_value=1000, max_value=9999, label='Third Code')
code4 = forms.IntegerField(initial=1000, min_value=1000, max_value=9999, label='Fourth Code')
class Meta:
model = Models.Lock
fields = ('lockName','newNameBool','newLockName','roomLoc', 'state', 'code1', 'code2', 'code3', 'code4')
field_order = ['lockName', 'newNameBool', 'newLockName', 'roomLoc', 'state', 'code1', 'code2', 'code3', 'code4']
views.py
def changeLock(request):
if request.method == "POST":
# if get post request will create form that has request.POST data!
form = changeLockForm(request.POST, user=request.user)
if form.is_valid():
locksObj = Model.Lock()
# will tell us if form valid when submitted
# form.cleaned_data is a DICTIONARY
tempBool = form.cleaned_data['newNameBool']
if tempBool:
locksObj.lockName = form.cleaned_data['newLockName']
elif not tempBool:
locksObj.lockName = form.cleaned_data['lockName']
else:
print("WHOOPSIE! clearly I coded something wrong wtf")
locksObj.state = form.cleaned_data['state']
locksObj.roomLoc = form.cleaned_data['roomLoc']
locksObj.code1 = form.cleaned_data['code1']
locksObj.code2 = form.cleaned_data['code2']
locksObj.code3 = form.cleaned_data['code3']
locksObj.code4 = form.cleaned_data['code4']
locksObj.owner = request.user
# get the old alarm name
allCodes = Model.Lock.objects.filter(owner_id=request.user)
# old Alarm Name
currentName = allCodes.values_list('lockName', flat=True)[0]
# if the old alarm name and new alarm name aren't equal...
# delete the old alarm entry
entry = Model.Lock.objects.get(lockName=str(currentName))
entry.delete()
# replace it with the new data
locksObj.save()
messages.success(request, 'Lock ' + str(locksObj.lockName) + ' edited!')
return redirect("status-page")
else:
# if not POST request, will create a blank form!
form = changeLockForm(user=request.user)
return render(request, 'users/changeLockForm.html', {'form': form})

Dynamic content on page is not showing

I am trying to generate dynamic content on page with the onclick event of js but there is error in console "Uncaught SyntaxError: Unexpected identifier"
html
{% load static %}
<head>
<script type="text/javascript">
function myFunction() {
// document.getElementById("demo").innerHTML = "Paragraph changed.";
document.getElementById("demo").innerHTML = "{{ Malegender }}";
}
</script>
</head>
<body>
<div class="container mt-5">
<h1>Gender</h1>
<hr>
<h5>{{ mygender.as_p }}</h5>
<h1 id="demo">Hello</h1>
<div class="container">
<button type="submit" name="gender_submit" class="btn btn-success" onclick="myFunction()">Confirm</button>
</div>
</div>
</body>
views.py
def Gender(request):
gender_selection = GenderForm()
male_detail = MaleForm()
Male = False
print("Value of male",Male)
if 'gender_submit' in request.POST:
male_detail = MaleForm(data=request.POST)
Male = True
print("Value of male d",Male)
print("Value of male a",Male)
return render(request, 'Gender.html',{"mygender":gender_selection,"Malegender":male_detail})
forms.py
class MaleForm(forms.ModelForm):
class Meta:
model = GenderModel
fields = ('genderMale','genderMale1','genderMale2','genderMale3','genderMale4')
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
# Create your models here.
class GenderModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
gender_choice=(('Male','Male'),('Female','Female'),)
gender = models.CharField(max_length=6,choices=gender_choice,null=True,)
genderMale = models.BooleanField(default=False)
genderMale1 = models.BooleanField(default=False)
genderMale2 = models.BooleanField(default=False)
genderMale3 = models.BooleanField(default=False)
genderMale4 = models.BooleanField(default=False)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
GenderModel.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.gendermodel.save()
when I click on button the error on the console is showing. I am trying to display that content when the user clicks on the button
this question suggests never using the onclick attribute. Instead you can try using an event listener like this:
<button type="submit" name="gender_submit" class="btn btn-success"
id="gender_submit_button">Confirm</button>
<script type="text/javascript">
function myFunction() {
document.getElementById("demo").innerHTML = "{{ Malegender }}";
}
document.getElementById('gender_submit_button').addEventListener('click', myFunction);
</script>
Also, make sure that your script is placed after the button and demo.

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...

Categories