Why doesn't the 'follow' button on django work? - javascript

I'm using Django 2.2 and PostgreSql. I'm trying to create a simple app that I want to follow neighboring users. 'Follow' button will increase the number of followed, 'Unfollow' button will decrease the number of followed. However, the 'Follow' button does not work. How can I solve this problem?
following/models.py
class Following(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='fallower', null=True)
followed = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='fallowing')
following/views.py
def user_follow_unfollow(request):
response = sub_user_follow_unfollow(request)
data = response.get('data')
followed = response.get('followed')
numbers_followed_and_follower= Following.user_followed_and_follower(followed)
context = {'user': followed, 'followers': numbers_followed_and_follower['followers'],
'followeds': numbers_followed_and_follower['followeds']}
html = render_to_string('following/following_partion.html', context=context, request=request)
data.update({'html': html})
return JsonResponse(data=data)
def sub_user_follow_unfollow(request):
if not request.is_ajax():
return HttpResponseBadRequest()
data = {'follow status': True, 'html': '', 'is_valid': True, 'msg': '<b>Unfollow</b>'}
follower_username = request.GET.get('follower_username', None)
followed_username = request.GET.get('followed_username', None)
follower = get_object_or_404(User, username=follower_username)
followed = get_object_or_404(User, username=followed_username)
does_follow_the_user= Following.user_does_follow_the_user(follower=follower, followed=followed)
if not does_follow_the_user:
Following.user_follow(follower=follower, followed=followed)
else:
Following.user_unfollow(followed=followed, follower=follower)
data.update({'msg': '<b>Follow</b>', 'follow_status': False})
return {'data': data, 'followed': followed}
templates.following_partion.html
{% if request.neighbor_detail != user %}
<div>
<button followed='{{ neighbor_detail.username }}' followed='{{ request.neighbor_detail.username }}'
url="{% url 'following:user_follow_and_unfollow' %}" id="follow_unfollow_button"
class="btn btn-lg btn-success">
{% if does_follow_the_user%}
<b>Unfollow</b>
{% else %}
<b>Follow</b>
{% endif %}
</button>
</div>
{% endif %}
<div class="followers col-lg-offset-3 col-md-3 col-md-offset-3 col-lg-3 text-center">
<span><b>followers</b></span>
<button url="{% url 'following:fallowed-or-fallowers-list' 'fallowers' %}" follow_type="followers"
username="{{ neighbor_detail.username }}" class="follow_button btn-block btn btn-primary">
{{ followers}}
</button>
<div class="followeds col-lg-3 col-md-3 text-center">
<span><b>Followeds</b></span>
<button url="{% url 'following:followed-or-followers-list' 'followed' %}" follow_type="followed"
username="{{ neighbor_detail.username }}" class="follow_button btn-block btn btn-success">
{{ followeds}}
</button>
my script
<script>
$("#follow_unfollow_button").click(function () {
var $this = $(this);
var $url = $this.attr('url');
var $takip_eden = $this.attr('follower');
var $takip_edilen = $this.attr('followed');
var data = {follower_username: $follower, followed_username: $followed};
$.ajax({
url: $url,
type: 'GET',
dataType: 'json',
data: data,
success: function (data) {
if (data.is_valid) {
$this.html(data.msg);
$("#user_following").html(data.html)
}
}
})
});
</script>

We cannot help you as there's too many missing pieces. However, this should not be too difficult to debug, here are some things you should inspect:
In your browser developer tools, inspect the HTTP request (XHR request). What parameters are being sent?
In your python debugger, inspect the values of request.GET, data, numbers_followed_and_follower. If you don't know these, it's impossible to tell what's wrong.
If it's still not clear what's wrong, in your python IDE, set a breakpoint in your view and step through your code line by line, inspecting the results. I cannot stress how important it is to use an IDE with a proper debugger.
In your browser developer tools, inspect the HTTP response to your AJAX request. What is being returned?
You need to analyse step-by-step your various variables to understand where's the issue.
Suggestions
You should really use POST (not GET) in your AJAX call, since you're modifying the database.
Use a ManyToManyField to register followers. They are just a relationship between two users:
follows = models.ManyToManyField("self", symmetrical=False, related_name="followers")
By specifying symmetrical=False you tell Django to distinguish the two directions of the relationship. In order to create or remove the relationship in your view, and to view relationships, you can just:
user.follows.add(other_user) # user follows other_user
user.follows.remove(other_user) # user unfollows other_user
user.followers.all() # people following
user.followers.count() # number of followers

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 %}"

How can I dispatch URLs in the form sitename.com/<slug>/<slug> in django?

I am trying to create a website whose structure is something like this:
A main page with various links of similar kind (like a blog). User can click on any on those and go inside any of them (link will be like site.com/slug). Then again the same thing (this time the link will be like site.com/slug/slug).
individual_question.html
<div id="argument_container">
{% for argument in argument%}
<h6 id='{{ argument.argument_faction }}' style="display:none">{{ argument.argument_text }}
<br><br>
<button type="button" class="btn btn-outline-success btn-sm"><i class="fa fa-thumbs-o-down" style="padding-right: 3px;"></i>Agree</button>
<button type="button" class="btn btn-outline-danger btn-sm"><i class="fa fa-thumbs-o-up" style="padding-right: 3px;"></i>Disagree and debate</button>
</h6>
{% endfor %}
</div>
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', home_screen_view, name="home"),
path('register/', registration_view, name="register"),
path('logout/', logout_view, name="logout"),
path('login/', login_view, name="login"),
path('account/', account_view, name="account"),
path('<slug:question>', individual_question_view, name="individual_question_view"),
# for testing
path('<slug:argument>', individual_argument_view, name="individual_argument_view"),
models.py
class Faction(models.Model):
faction_id = models.BigAutoField(primary_key=True)
faction_question = models.ForeignKey(Question, on_delete=models.CASCADE)
faction_name = models.CharField(max_length=30)
def __str__(self):
return self.faction_name
class FactionArgument(models.Model):
argument_id = models.BigAutoField(primary_key=True)
argument_faction = models.ForeignKey(Faction, on_delete=models.CASCADE)
argument_question = models.ForeignKey(Question, on_delete=models.CASCADE, null=True)
argument_slug = models.SlugField(max_length=80, null=True)
argument_text = models.TextField()
user_id = models.ForeignKey(Account, on_delete=models.CASCADE)
datetime = models.DateTimeField(auto_now=True)
def get_absolute_url(self):
return reverse('individual_argument_view', args=[self.argument_slug]) # chances of error
def __str__(self):
return self.argument_text
views.py
def individual_question_view(request, question):
context={}
question = get_object_or_404(Question, question_slug=question)
context["question"] = question
factions = Faction.objects.filter(faction_question = Question.objects.filter(question = question.question).first())
context["faction"] = factions
arguments = FactionArgument.objects.filter(argument_question = Question.objects.filter(question = question.question).first())
context["argument"] = arguments
return render(request, "snippets/individual_question.html", context)
# for testing
def individual_argument_view(request, argument):
return render(request, "snippets/individual_chain.html")
I am able to get to the (site.com/slug) but not able to figure out the url dispatching for next step (site.com/slug/slug).
Currently it shows this error:
Page not found (404) Request Method: GET Request URL: http://127.0.0.1:8000/question_1/blue2 Using the URLconf defined in Version2.urls, Django tried these URL patterns, in this order:
admin/ [name='home'] register/ [name='register'] logout/ [name='logout'] login/ [name='login'] account/ [name='account'] <slug:question> [name='individual_question_view'] <slug:argument> [name='individual_argument_view'] password_change/done/ [name='password_change_done'] password_change/ [name='password_change'] password_reset/done/ [name='password_reset_done'] reset/<uidb64>/<token>/ [name='password_reset_confirm'] password_reset/ [name='password_reset'] reset/done/ [name='password_reset_complete'] The current path, question_1/blue2, didn’t match any of these.
You need to modify the endpoint signature in the urls.py file.
Right now it reads
path('<slug:question>', individual_question_view, name="individual_question_view"),
but this covers only one slug.
path('<slug:question>/<slug:param2>', individual_question_view, name="individual_question_view"),
This will pass 2 params to the view.
Next, the href param in the template does not work as is due to concatenation of two separate urls instead of one with two params:
href=" {{question.get_absolute_url}}{{argument.get_absolute_url}} "
it should be instead:
href="{% url 'individual_question_view' question.question_slug argument.argument_slug %}"
As a side note, the urls.py posted has a duplicate signature, in effect the second one will not do anything because the first one will catch it and stop searching:
path('<slug:question>', individual_question_view, name="individual_question_view"),
# for testing
path('<slug:argument>', individual_argument_view, name="individual_argument_view"),
Updated: follow-up issue discussed in answer comments.

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

Django, how to handle fetch request

I'm working on a Django project that uses a flatbed scanner. Since scanning takes a long time I must work around getting a timeout error. After searching and trying multiple things I ended up with threading an a fetch call.
How do I alter the fetch call to do what I want? I currently get send to a blank page that shows the dictionary that was returned by free_scan_crop. Please note that I am new to JavaScript. I just copied this bit of JS.
What I would like to happen:
A modal shows up when the form is submitted
When the scanner is done: send user to home page and show message
scan.html
<div class="modal fade" id="scanDocument" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Scanning</h5>
</div>
<div class="modal-body">
Please wait...
</div>
</div>
</div>
</div>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch("{% url 'core:free_scan' %}", {
method: 'GET',
body: new FormData(formElem)
});
let result = await response.json();
alert(result.message);
};
</script>
views.py
def free_scan_crop(request):
form = FreeScanForm(request.GET)
if form.is_valid():
file_name = form.cleaned_data['file_name']
# Grab the rest of the fields from the form...
x = threading.Thread(
target=scan_crop,
args=(request, file_name, top_left_x, top_left_y, bottom_right_x, bottom_right_y, dpi),
)
return x.start() # Questions: is this correct?
return JsonResponse({"scanning": True})
# invalid form
return JsonResponse({"scanning": False})
def scan_crop(request, file_name, top_left_x, top_left_y, bottom_right_x, bottom_right_y, dpi):
# This method runs for a long time
image = ScannerServiceConnection().scan_roi(
top_left_x,
top_left_y,
bottom_right_x,
bottom_right_y,
dpi
)
if image is None:
# No connection to the scanner
messages.error(request, 'Check scanner service status')
else:
# store image
image.save(<file_path>)
messages.success(request, 'saved image')
# Please note that I want to send a message to the user to inform them on the status of the scan
return render(request, 'home.html')
A special thanks to vlaz for helping out via the JS chat.
The fetch call is now working like it should. I couldn't get fetch to show the modal so made a little function to do that.
The scanner is running as a service via rpyc. I had to disable the timeout setting to keep it from throwing timeout errors. Note, this Django application runs offline on the user's system.
rpyc.connect(
host="localhost",
port=18861,
config={'sync_request_timeout': None},
)
scan.html
<form class="form" action="" method="post" id="free_scan_from">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" id="save_button" class="btn btn-primary" onclick="modalFunction()" name="action"
value="save_crop">
<i class="fa fa-save"></i> Scan & Save
</button>
{% endbuttons %}
</form>
<script>
// show the modal when the file_name field has a value
function modalFunction() {
if (document.getElementById("id_file_name").value !== "") {
$('#scanBanknote').modal('show');
}
}
</script>
<script>
const formElem = document.querySelector("free_scan_from")
formElem.onsubmit = async (e) => {
e.preventDefault();
let response = await fetch("{% url 'core:free_scan' %}", {
//cannot have a body with GET
method: 'POST',
body: new FormData(formElem)
});
let result = await response.text();
alert(result);
};
</script>
views.py
def free_scan_crop(request):
form = FreeScanForm(request.POST)
if form.is_valid():
file_name = form.cleaned_data['file_name']
# grab the rest of the fields
image = ScannerServiceConnection().scan_roi(
top_left_x,
top_left_y,
bottom_right_x,
bottom_right_y,
dpi
)
if image is None:
messages.error(request, 'Check scanner service status')
else:
# store image
image.save(<file_path>)
messages.success(request, 'image saved')
return render(request, 'free_scan_preview.html')
# invalid form
context = {'form': form}
return render(request, "free_scan_crop.html", context)

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