Django: User Javascript object as context variable - django

I have been having this problem for long. But I am not able to figure how to do this. Is there a way so, we can use the javascript objects with the template language. For example, I have queryset of categories which are rendered on a select widget.
Category: <select name="category" id="id_category">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
Now, for the selected category, I asynchronously call the server to return the related Product Types.
$('#id_category').change(function(){
getProductTypes($(this).val());
});
In the ajax function, I am unable to use the category_id js object in the url template tag. Due to this I am bound to hard code the target url. Can anyone please suggest a way to handle this. Thanks
function getProductTypes(category_id){
//Would like to do this
var url = {% url lookup_product_types category_id %}
//But end up doing this
var url = '/'+category_id+'/product_types/find/'
$.ajax({
url:url,
data:{category:category_id},
dataType:'json',
success: function(data, status, xhr){
html = '<select>';
$.each(data, function(index, value){
html += '<option value='+this.pk+'>'+this.fields.name+'</option>';
});
html += '</select>';
$('#productType').html(html);
}
});

How about using a data attribute on the option tags?
You would update your template to something like -
{% for category in categories %}
<option data-url="{% url lookup_product_types category.id %}" value="{{category.id}}">{{category.name}}</option>
{% endfor %}
Then change the ajax along the lines of -
$('#id_category').change(function(){
var url = $(this).find('option:selected').attr('data-url');
getProductTypes($(this).val(), url);
});
function getProductTypes(category_id, url){
$.ajax({
url: url,
data:{ category:category_id},
// ..

Related

Django - Ask users to confirm change of PDF file

I got a question regarding Fileupload. I got a form where users can upload PDFs and change the uploaded PDFs. When they change the PDF, I want to add a warning, asking them to confirm the PDF change. Any idea what's the best way of doing it?
Right now, I'm trying to solve it with JS in my HTML, like so:
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
<input class="deleter" type="submit" value="Delete">
{% include "some_html.html" %}
</form>
<script>
// Add event listener to change button
function confirmFileChange() {
var fileInput = document.getElementById('id_file');
fileInput.addEventListener('change', function() {
if (fileInput.value) {
if (!confirm('Are you sure you want to change the uploaded PDF?')) {
fileInput.value = '';
}
}
});
}
if (document.body.innerHTML.indexOf('Change: ') !== -1) {
confirmFileChange();
}
</script>
But this also displays the warning upon first upload, when the user isn't changing anything.
I assume that you render your HTML in the django view with a command like render(request, 'simple_upload.html').
The render command accept as third parameter a context object, where you can store information, that can be used in the template. You have to write in this context, if a PDF is already exists or not.
context = { 'pdf_already_exists': True if pdfAlreadyExists() else False }
render(request, 'simple_upload.html', context)
Now you can evaluate in the template if the PDF already exists.
{% if pdf_already_exists %}
<script>
var fileInput = document.getElementById('id_file');
fileInput.addEventListener('change', function() {
if (fileInput.value) {
if (!confirm('Are you sure you want to change the uploaded PDF?')) {
fileInput.value = '';
}
}
});
</script>
{% endif %}
The script is only rendered in the HTML when a PDF already exists, and the warning can not be displayed upon first upload, when the user isn't changing anything.
I hope that helps.

How to get specific id from for loop in django template?

I'm working with django and javascript, I'm trying to follow and unfollow multiple users without refreshing the page. therefore i'm using ajax. The problem which i'm facing right now is that the first follower is getting follow and unfollow no matter if i click on that user or on other user.. its an obvious behavior because i couldn't understand how to get specific id of user from the for loop so that i can use that user in a js function.
{% if follower.user in request.user.userprofile.follower.all %}
<span><a class="btn btn-success" id="follow-button{{follow.id}}" toggle="{{follower}}" type="submit">{% csrf_token %}UnFollow</a></span>
{% else %}
<span><a class="btn btn-warning" id="follow-button{{follow.id}}" toggle="{{follower}}" type="submit">{% csrf_token %}Follow</a></span>
{% endif %}
</div><!--/ followers-body -->
{% endfor %}
<script>
$(document).on('click','a[id^=follow-button]', function (e) {
var user = $('#follow-button').attr('toggle'); //this user is coming the first use of looping object..no matter if i click on the 2nd or 3rd user of the loop.
console.log(user,'im im im tested');
e.preventDefault();
$.ajax({
type: 'POST',
url: '{% url "profiles:toggle" %}',
data: {
user_toggle: user,
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'POST',
},
success: function (json) {
result = json['result']
console.log(result,"maii mai maima maima a")
if (result) {
document.getElementById("is_following").innerHTML = '<a class="btn btn-success" id="follow-button" toggle="{{user.userprofile}}" type="submit">{% csrf_token %}UnFollow</a>'
}
else
document.getElementById("is_following").innerHTML = '<a class="btn btn-warning" id="follow-button" toggle="{{user.userprofile}}" type="submit">{% csrf_token %}Follow</a>'
},
error: function (xhr, errmsg, err) {
}
});
})
</script>
It is more of JS/JQuery question than a Django one, but here is what I think you are missing.
When you add a listener to an element, you have an option to get the element that was interacted with using the saved word this.
Your problem in the code is that line where you retrieve user. If you replace $('#follow-button') with $(this) then you will get the user that was clicked.
From there you can retrieve the id easily.

How do I render all the objects of a child model belonging to a particular object of the parent model on click?

Suppose there are two models Lists and Tasks where 'Lists' have one-to-many relationship with 'Tasks'.
All the objects of the Lists model are rendered on the page like this:
HTML
<div class="grid-container">
{% for list in lists %} <!--lists is context for Lists.objects.all() -->
<div class="grid-item" id="{{ list.id }}" onclick="showTasks( {{ list.id }} )">
{{ list.name }}
</div>
{% endfor %}
</div>
<dialog id="tasks">
</dialog>
JavaScript
<script>
function showTasks(listid){
document.getElementById("tasks").show();
}
</script>
Now I want to render all the tasks_set (all the objects of 'Tasks') related to a particular object of 'Lists' in that dialog with id="tasks".
As it can be seen in the above snippet, I thought of doing it by passing list.id as a parameter to the JavaScript function but couldn't figure out beyond it. How can I achieve it?
Solution 1:
Building on Iain's comment some code. I think it is easiest if you use jQuery so load it in the section of your html template, e.g.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
Then you need a view that returns the required tasks data
def tasks_view(request):
list_id = request.GET.get('listid') # fetch the id of the list
tasks = Lists.objects.get(pk=list_id).tasks.all() # get your tasks
data = {'tasks': render_to_string('tasks.html', {'tasks': tasks})} # pre render the data
return JsonResponse(data)
A remark about the view: You can of course also return the raw Json data. However, in your case I think it is easier to create a small sub-template (tasks.html in the example) and use render_to_string to get the html code you can simply add to your base html page. Don't forget to add the view to your urls.
An example task.html just for the completeness:
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
Then send an Ajax request to the view (tasks_view) when clicked.
<script>
function showTasks(listid){
$.ajax({
url: '/tasks/', // url of the view created above
data: {
'listid': listid // Your list id
},
data_type: 'html', // as we are receiving a html template
success: function(data){
$('#tasks').append(data.tasks); // append the html code to the dialog
$('#tasks').show();
}
});
}
</script>
Solution 2
In case you do not want to use Ajax and do not mind rendering all your tasks on loading the template you can also create a for the tasks of each list and show them on demand. For this iterate through your lists:
{% for list in lists %}
<dialog id="list-{{ list.id }}">
<ul>
{% for task in list.tasks.all %}
<li>{{ task }}</li>
{% endfor %}
</ul>
</dialog>
{% endfor %}
As you can see you can access the m2m field 'tasks' of each Lists object with list.tasks.all (no ()!). And each has got an individual id.
And then just show and hide the dialogs (just as an example w/o jQuery in case you are reluctant to use it):
<script>
function showTasks(listid){
// Close all dialogs
var all_dialogs = document.getElementsByTagName('dialog');
for (i = 0; i < all_dialogs.length; i++){
all_dialogs[i].removeAttribute('open');
}
// Open the required dialog
var dialog = document.getElementById("list-" + listid);
dialog.setAttribute('open','open');
};
</script>

How to use django-markdownx in my view in similar way to admin?

I'm stuck using django-markdownx to automatically update page and to submit changes.
I followed this question and answer and managed to get django-markdownx working in admin, and within my view. However in my view editing the textarea does not automatically update the page.
The admin page with django-markdownx is exactly what I want, updating the textarea updates the page, but not the underlying database field until you hit save.
So I then tried to rip out the admin code into my own view.
In my view/template I have a form, textarea similar to admin one. I also included "/static/markdownx/js/markdownx.js" and set my form to mostly be similar to the admin page:
<form method="POST" action="">{% csrf_token %}
<div class="markdownx">
<textarea name="myfield" rows="10" cols="40" required="" data-markdownx-upload-urls-path="/markdownx/upload/" data-markdownx-editor-resizable="" class="markdownx-editor" id="id_myfield" data-markdownx-urls-path="/markdownx/markdownify/" data-markdownx-latency="500" data-markdownx-init="" style="transition: opacity 1s ease;">
{{ note.myfield }}
</textarea>
</div>
<div class="markdownx-preview">
{{ note.formatted_markdown|safe }}
</div>
</form>
This didn't work.
I see periodically there is requests to /markdownx/markdownify/ when you edit in admin, but not mine. I'm not sure if I should aim to do the same or just do some timed javascript page refresh and pass all the data from within my form back to my view to then re-render the page again.
I can't quite get my head around the django-markdownx documentation.
UPDATE:
The Documentation seems to suggest that a call to MarkdownX() should do the initialisation.
<script src="/static/markdownx/js/markdownx.js"></script>
...
<script type="text/javascript">
let parent = document.getElementsByClassName('markdownx');
let md = new MarkdownX( element, element.querySelector('.markdownx-editor'), element.querySelector('.markdownx-preview'));
</script>
But when I try this I get.
Uncaught ReferenceError: MarkdownX is not defined
Also I don't see any initialisation like this within the admin page.
Is there an example of using the django-markdownx in your own views similar to the usage within admin?
Thanks
LB
The following is a broken solution.
The correct method would be to use the MarkdownX's built-in Javascript, but I just can't get it to work, yet. So, I wrote my own. It may be of use to others.
In template html, include js.cookie.min.js in order to get the django csrftoken. Then a callback function which will be called when a change is made to the textarea. We then update the preview div with HTML code we received back from MarkdownX's markdownify call.
<script src="https://cdn.jsdelivr.net/npm/js-cookie#2/src/js.cookie.min.js"></script>
...
<script type="text/javascript">
function myMDFunc( elem ) {
input = elem.value;
var csrftoken = Cookies.get('csrftoken');
$.ajax(
{
type: "POST",
url: "/markdownx/markdownify/",
data: { CSRF: csrftoken, csrfmiddlewaretoken: csrftoken, content: input}
})
.done(function(data, status){
document.getElementById("markdownx-preview").innerHTML = data;
});
}
</script>
Still in the template html, in the form, call this function both for onchange and onkeyup.
<form method="POST" action=""> {% csrf_token %}
{{ note.title }}
<div class="markdownx">
<textarea onchange="myMDFunc(this)" onkeyup="myMDFunc(this)" cols="60" rows="5" name="text" >
{{ note.myfield }}
</textarea>
</div>
<div class="markdownx-preview" id="markdownx-preview">
{{ note.formatted_markdown|safe }}
</div>
<input type="submit" id="submit" name="submit">
</form>
In summary, a change to the textarea means we invoke the 'onchange' or 'onkeyup', which calls myMDFunc. Then myMDFunc does an ajax call with data which is the raw MarkDown code, the response to this call is the pretty HTML data. The callback within myMDFunc updates the preview with that pretty HTML.
It kinda works. I'm sure the real MarkdownX code will handle drag'n'drop of images and pacing the ajax calls to be nice to the server.

how to access jQuery.ajax() get parameters in Django views

I recently started to learn jQuery and right now I am playing around with .ajax() function.
I cannot figure out how to access the get parameters in Django.
My code looks like:
Jquery & html:
<div id="browser">
<ul>
{% comment %}
Theres a script for each ctg. Each script fades out #astream, fades in #stream_loading and then it should display #astream with new values based on the GET param in ajax call
Prolly it wont work, but first I need to interact with the GET param in my views.py
{% endcomment %}
{% for ctg in ctgs %}
<script type="text/javascript" charset="utf-8">
(function($) {
$(document).ready(function() {
$("#stream_loading").hide()
$("#browse_{{ctg}}").click(function() {
$("#astream").fadeOut()
$("#stream_loading").fadeIn()
$.ajax({
type: "GET",
url: "/{{defo}}/?param={{ctg}}",
success: function() {
$("#stream_loading").fadeOut()
$("#astream").fadeIn()
}
});
});
});
})(jQuery);
</script>
<li><a id="browse_{{ctg}}" title="{{ctg}}">{{ctg}}</a></li>
{% endfor %}
</ul>
</div>
<div id="astream">
{{ajaxGet}} #just to see whats rendered
{% include "astream.html" %}
</div>
<div id="stream_loading">
loading stream, please wait ...
</div>
Django:
#https_off
def index(request, template='index.html'):
request.session.set_test_cookie()
path=request.META.get('PATH_INFO')
defo=path[1:path[1:].find('/')+1]
request.session['defo']=defo
defo=request.session['defo']
# build the stream sorted by -pub_date
import itertools
chained=itertools.chain(
model1.objects.order_by('-pub_date').filter(),
model2.objects.order_by('-pub_date').filter(),
)
stream=sorted(chained, key=lambda x: x.pub_date, reverse=True)
ajaxGet=request.GET.get('param','dummy')
if request.is_ajax():
template='astream.html'
ajaxGet=request.GET.get('param',False)
renderParams={'defo':defo, 'stream':stream, 'ajaxGet':ajaxGet}
return render_to_response(template, renderParams, context_instance=RequestContext(request))
Then I try to show it up in my template
{{ ajaxGet }}
But everytime is rendered as 'dummy'
In firebug I can see get requests with proper key and value.
What do I miss here?
Thanks
There is a frequent gotcha that people often fall into when doing this kind of Ajax, and that is not preventing the default action of the link/button. So your Ajax function never has a chance to fire, and the request that you're seeing in the Django code is caused by the normal page load - which is why is_ajax() is false.
Give the click handler a parameter, event, and call event.preventDefault(); at the end of the function.