Django: Using DataTables - django

I want to introduce filtering and sorting functionality to my table using ajax. From what I found, DataTables seamed to be the best option. but, I can't seem to get it working! The first thing I tried was using it just how they have it set up with there demos. But I could not seam to get the search form to generate and the sorting wont work either. I then tried to use one of the many packages created to implement that functionality. However, I found that the documentation was usually not very clear and difficult to follow, or even when I did follow it, I would be left with the table rendering fine, but the search and sort would still not be available. So I've decided to go back to my original and see if someone might know what I'm doing wrong. The page does render the table correctly, and if I view page source, the javascript is properly linked.
Here is the html:
<pre>
<code>
{% extends "theme_bootstrap/base.html" %}
{% load staticfiles %}
{% block extra_style %}
<script src="{% static "js/jquery.js" %}"></script>
<script src="{% static "js/jquery.dataTables.js" %}"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#test').dataTable();
});
</script>
{% endblock %}
{% block body %}
{{ store.StoreName}}
{% if liquors.count > 0 %}
<h1>liquors</h1>
<table id="test">
<thead>
<tr>
<th>Liquor Code</th>
<th>Brand Name</th>
<th>Vendor Name</th>
</tr>
</thead>
<tbody>
{% for liquor in liquors %}
<tr>
<td>{{ liquor.LiquorCode }}</td>
<td>{{ liquor.BrandName }}</td>
<td>{{ liquor.VendorName }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>None to show!</p>
{% endif %}
{% endblock %}
</code>
</pre>
Here is also my view. Perhaps I've done something wrong here.
def liquors(request, store_id):
args = {}
args.update(csrf(request))
a = Store.objects.get(StoreID=store_id)
args['liquors'] = Liquor.objects.all()
args['a'] = a
return render(request, 'liquors.html', args)

I did this on a project I worked on a while back. Here's how I defined the table in my template:
$(document).ready( function () {
var ticketTable = $('#ticket-table').dataTable( {
"fnDrawCallback": function() {
// Initialize popovers anytime new data is loaded into the table
$('a[rel=tooltip]').tooltip({
placement: 'left'
});
},
"bServerSide": true,
"bAutoWidth": false,
"sPaginationType": "bootstrap",
"sAjaxSource": "{% url get_tickets_list %}",
"aaSorting": [[ 2, "desc" ]],
"iPageLength": 25,
"bLengthChange": false,
"bStateSave": true,
"bFilter": true,
"sDom": "<'length-change'><'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'length-change'><'span6'p>>",
"oLanguage": {
"sSearch": ""
},
"aoColumns": [
{ "bSortable": false, "sWidth": "14px", "bSearchable": false },
{ "sWidth": "160px", "bSearchable": true },
{ "sWidth": "60px", "bSearchable": true },
{ "bSearchable": true },
{ "bSortable": false, "sWidth": "14px", "bSearchable": false },
{ "sWidth": "50px", "sClass": "center", "bSearchable": false },
{ "bSearchable": true },
{ "sWidth": "70px", "sClass": "center", "bSearchable": false },
{ "sWidth": "75px", "bSearchable": true }
] } ).fnSetFilteringDelay(500);
Key is this line in the table options which defines the source URL for the AJAX request from the DataTable:
"sAjaxSource": "{% url get_tickets_list %}",
Then, you'll also need a view to return the AJAX requests:
def get_tickets_list(request, queryset=Ticket.objects.all()):
"""
AJAX calls for DataTable ticket list.
"""
#columnIndexNameMap is required for correct sorting behavior
columnIndexNameMap = { 0: 'link', 1 : 'created', 2: 'priority', 3: 'client', 4: 'client_email', 5: 'sites', 6: 'problem',7: 'updates', 8: 'state' }
#path to template used to generate json (optional)
jsonTemplatePath = 'json_tickets.txt'
#call to generic function from utils
return get_datatables_records(request, queryset, columnIndexNameMap, jsonTemplatePath)
As I said, this was a while ago and may not work anymore with the latest versions of DataTable, but maybe it will set you in the right direction.

Related

Issues with csfr token and ajax call

I tried to implement a button on a Website, which upon pressing it automatically runs a python function on the server and copies some value into the clipboard of the user. The clipboard copying runs fine, but I can not run the python function.
Whenever I try to I get an error 403 and I think it is due to an issue with the csfr token. Can anyone help me to solve this issue?
Here is my HTML
{% if categories %}
<div class="card shadow mb-4">
<div class="card-body card-interface">
<table id="predictionTable" class="table table-bordered">
<thead>
<tr>
<th>Vorhersage</th>
<th>Wahrscheinlichkeit</th>
<th>Kopieren</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td>{{ category.0}}</td>
<td>{{ category.1}}%</td>
<td><img src="{% static "documents/img/copy.png" %}" class="interface-copy" value="{{ category.0 }}" input_text = "{{ input_text }}" style="cursor: pointer"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div id="django-data" data-CSRF="{{csrf_token}}"></div>
And here the .js that is run
$(".interface-copy").on('click', function(e) {
var csrftoken = $("django-data").data().CSRF;
console.log(csrftoken);
console.log("test")
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(this).attr('value')).select();
document.execCommand("copy");
$temp.remove();
console.log("test")
$.ajax({
url: "/ajax/postSingleSourceEntry/",
type : 'POST',
beforeSend: function(request){
request.setRequestHeader("X-CSRFToken", csrftoken);
},
data: {
csfrmiddlewaretoken: csrftoken
},
dataType: "json",
success: function (data){
console.log("call created")
},
error : function(response){
console.log(response)
}
})
});
Change:
<div id="django-data" data-CSRF="{{csrf_token}}"></div>
To:
<div id="django-data" data-csrf="{{csrf_token}}"></div>
And:
var csrftoken = $("django-data").data().CSRF;
To:
var csrftoken = $("#django-data").data().csrf; // Note the # before django-data and csrf in small letter.
You might want to read: How to get the data-id attribute?
You can add #csrf_exempt decorator for that ajax function

Vue.js For loop is not rendering content

I am building a web application using vue.js, vue-resource, vue-mdl and google material design lite.
JS compilation is performed using webpack through laravel elixir.
In this app I have to render a table row for each object from an array returned from a Rest API (Django Rest Framework). I have made the following code inside the html to render content using vue.js:
<tr v-for="customer in customers">
<td class="mdl-data-table__cell--non-numeric">{{ customer.status }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.name }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.company }}</td>
<tr>
This should render all objects in the array as a table row. I have also tried to wrap the above in a template tag like this:
<template v-for="customer in customers">
<tr>
<td class="mdl-data-table__cell--non-numeric">{{ customer.status }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.name }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.company }}</td>
</tr>
</template>
This did not change either.
I have also tried to hardcode the array inside the ready() function of the vue instance, but this did not help either.
window._ = require('lodash');
require('material-design-lite');
window.Vue = require('vue');
require('vue-resource');
var VueMdl = require('vue-mdl');
Vue.use(VueMdl.default);
const app = new Vue({
el:'body',
ready: function(){
//this.getCustomerList();
this.customers = [
{ name: "Peter", status: "true", company: "Company 1"},
{ name: "John", status: "false", company: "Company 2"}
]
},
data: {
customers: [],
response: null,
messages: []
},
methods: {
getCustomerList: function()
{
this.$http({url: '/api/customers/', method: 'GET'}).then(function(response){
//Success
this.customers = response.data
console.log(response.data)
},
function(response){
console.log(response)
})
}
}
})
Changing the above to this does not change either:
window._ = require('lodash');
require('material-design-lite');
window.Vue = require('vue');
require('vue-resource');
var VueMdl = require('vue-mdl');
Vue.use(VueMdl.default);
const app = new Vue({
el:'body',
ready: function(){
//this.getCustomerList();
},
data: {
customers: [
{ name: "Peter", status: "true", company: "Company 1"},
{ name: "John", status: "false", company: "Company 2"}
],
response: null,
messages: []
},
methods: {
getCustomerList: function()
{
this.$http({url: '/api/customers/', method: 'GET'}).then(function(response){
//Success
this.customers = response.data
console.log(response.data)
},
function(response){
console.log(response)
})
}
}
})
I have also tried to just make a plain html table that does not have any of the Google MDL classes applied, and this does also not give any result.
Logging this.customers to the console shows that it does in fact contain the data, but for reason it is not rendering. Why is that? What am I doing wrong?
Here's a snippet of your code, which works as expected. I've added in CDN references to the libraries you mentioned, but I'm not doing anything with them. I offer this as a starting point for you to see if you can find what changes will reproduce your problem here.
const app = new Vue({
el: 'body',
ready: function() {
//this.getCustomerList();
this.customers = [{
name: "Peter",
status: "true",
company: "Company 1"
}, {
name: "John",
status: "false",
company: "Company 2"
}]
},
data: {
customers: [],
response: null,
messages: []
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue-resource/0.9.3/vue-resource.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.0/material.indigo-pink.min.css">
<script defer src="https://code.getmdl.io/1.2.0/material.min.js"></script>
<script src="https://rawgit.com/posva/vue-mdl/develop/dist/vue-mdl.min.js"></script>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--3-col">
<button id="create-customer" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" #click="$refs.createCustomer.open">
Create customer
</button>
</div>
<div class="mdl-cell mdl-cell--3-col">
<button id="convert-reseller" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
Convert to reseller
</button>
</div>
<div class="mdl-cell mdl-cell--3-col">
<button id="remove-customer" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
Remove Customer
</button>
</div>
<div class="mdl-cell mdl-cell--3-col">
<button id="change-status" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
Change status
</button>
</div>
</div>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<table class="mdl-data-table mdl-js-data-table mdl-data-table--selectable" style="width:100%;">
<thead>
<tr>
<th class="mdl-data-table__cell--non-numeric">Status</th>
<th class="mdl-data-table__cell--non-numeric">Customer name</th>
<th class="mdl-data-table__cell--non-numeric">Company</th>
</tr>
</thead>
<tbody>
<tr v-for="customer in customers">
<td class="mdl-data-table__cell--non-numeric">{{ customer.status }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.name }}</td>
<td class="mdl-data-table__cell--non-numeric">{{ customer.company }}</td>
</tr>
</tbody>
</table>
</div>
</div>
It seems now to be working.
Inside app.js I had:
const app = new Vue({ el: 'body' })
That, for some reason, conflicted with the one I was creating inside customer_list.js—although my methods worked fine.

JQuery's DataTables Editor plugin and Django

Integrating the DataTables plugin was straightforward and getting the Editor add-on integrated was also pretty painless - up to a point. However, the client/server side has been a bear for me.
The following is the JavaScript for DataTables and Editor. The part I cannot resolve is this snippet from the code that follows
var table = $('#theader').DataTable( {
**bProcessing: true,
bServerSide: true,
start: 1,
dataSrc: "id",
sAjaxSource: 'load_user_json',**
}
After the JavaScript is the html code relevant to the JavaScript.
The examples given, on the DataTables/Editor site, use PHP on the server side. I know zero about PHP and I cannot figure out how to replace it with Python to pass back JSON (via the code snippet above) to the Javascript that follows using Ajax which is a current requirement of the DataTables plugin.
Everything looks great. Everything work except getting the new/edit/delete action to work. I started with the following example on the DataTables/Editor site.
https://editor.datatables.net/examples/styling/bootstrap.html
JAVASCRIPT (DataTables / Editor)
$(document).ready(function() {
$(".dropdown-toggle").dropdown();
});
$(document).ready(function() {
$(".dropdown-toggle").dropdown();
});
$(document).ready(function edit_users() {
var csrftoken = getCookie('csrftoken');
var editorUser = new $.fn.dataTable.Editor( {
ajax: '',
table: "#theader",
fields: [ {
label: "ID:",
name: "ID"
}, {
label: "Name:",
name: "NAME"
}, {
label: "CM:",
name: "CM"
}, {
label: "Email:",
name: "EMAIL"
} ]
} );
if ( !$.fn.dataTable.isDataTable( '#theader' ) ) {
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
var table = $('#theader').DataTable( {
bProcessing: true,
bServerSide: true,
start: 1,
dataSrc: "id",
sAjaxSource: 'load_user_json',
columns: [
{ data: "ID" },
{ data: "NAME" },
{ data: "CM" },
{ data: "EMAIL" }
],
select: true
} );
}
new $.fn.dataTable.Buttons( table, [
{ extend: "create", editor: editorUser },
{ extend: "edit", editor: editorUser },
{ extend: "remove", editor: editorUser }
] );
table.buttons().container()
.appendTo( $('.col-sm-6:eq(0)', table.table().container() ) );
$('#theader tfoot th').each( function () {
var title = $(this).text();
$(this).html( '<input type="text" placeholder="Search '+title+'" />' );
} );
table.columns().every( function () {
var that = this;
$('input', this.footer() ).on( 'keyup change', function () {
if ( that.search() !== this.value ) {
that
.search( this.value )
.draw();
}
} );
} );
} );
HTML
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load staticfiles %}
{% block content %}
{% if queryset %}
<h2>Current Users</h2>
<table id="theader" class="table table-bordered table-hover small order-column">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CM</th>
<th>EMAIL</th>
</tr>
</thead>
<tfoot>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CM</th>
<th>EMAIL</th>
</tr>
</tfoot>
<tbody>
{% for row in queryset %}
<tr id=forloop.counter> <!-- Needed for DataTables row identification -->
<td>{{ row.operator_id }}</td>
<td>{{ row.fullname }}</td>
<td>{{ row.cm }}</td>
<td>{{ row.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<h4>No SKUs have been defined</h4>
{% endif %}
<script src="{% static 'js/searchable-users-table.js' %}"></script>
{% endblock %}
First things first; you don't have to provide a JSON endpoint to work with DataTables.js. You can render a table and call DataTable() on it.
Take the following example:
$(document).ready(function(){
$('#theader').DataTable({
pageLength:25,
rowReorder:false,
colReorder:true,
order: [[1, 'asc'],[0, 'asc']]
});
});
The table element at id theader is passed to DataTable, where the magic happens; DataTables will paginate, order, and allow for re-ordering based on this. If you don't know how to build a JSON endpoint, you can avoid it for now unless you truly need to have in-table editing.
If you do want to explore building a JSON API, Django Rest Framework is a great option and allows for fine control over serialization of models. This means that you can use the modular Viewsets and Serializers from DRF to build out all CRUD functionality for a given model / set of related models at a single endpoint.
However, for quick and dirty retrieval-only applications you can also build a view, call it via JS/JQuery's AJAX function on page load, and return a JsonResponse in your Django view. It's very quick and basically boils down to this:
Retrieve a queryset for a model based on some parameters (either provided via request.GET, request.POST, or url parameters)
Process as needed.
Convert an array of values (my_queryset=SomeModel.objects.filter(something=somevalue).values('field_1','field2'))
Serialize to JSON and respond, setting safe=False in the JsonResponse kwargs if not return a dictionary.(return JsonResponse(my_queryset, safe=False)). Alternately, convert your queryset to an ordered dict and pass it to JsonResponse.

Django links in Knockout Table

I have read this post: How can I create a text link in a Knockout javascript table? along with a couple others.
But, I am missing something somewhere, or not taking the right approach. I've included the relevant chunks of code for my problem. I am trying to use the table generated by knockout to to either update a task or remove a task. The remove part is working fine. I am trying to get the update to link to another page that is used to update the task. I cannot figure out what I need to do to get the link working properly in the update column.
I've tried several different approaches for how to put the url in the list of dictionaries that is passed to the KO model. Any advice to steer me in the right direction? If I am missing any information, please let me know. Thank you.
Views.py
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
task_row = {}
task_row['task_id'] = task.task_id
task_row['t_name'] = task.name
task_row['date'] = task.date_created
task_row['state'] = task.state
task_row['url'] = '{% url tracking:update_task task_id=task.task_id %}'
tasks_list.append(task_row)
json_tasks = json.dumps(tasks_list)
if request.POST:
json_data = request.POST.get('json_blob')
obj = loads(json_data)
task.task_id = obj.get("task_id")
remove_task = Tasks.objects.get(task_id=task.task_id)
remove_task.delete()
messages.success(request, 'Task removed')
HTML
<table>
<thead>
<th>Name</th>
<th>Date</th>
<th>State</th>
<th>Update</th>
<th>Remove</th>
</thead>
<tbody data-bind "foreach: tasks">
<tr>
<td data-bind="text: t_name"></td>
<td data-bind="text: date"></td>
<td data-bind="text: state"></td>
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
<td button class="btn" data-bind="click: $root.remove_task">Remove</button></td>
</tr>
</tbody>
</table>
{% block javascript_variables_nocompress %}
window.TASKS = {{ json_tasks|safe }};
{% endblock %}
{% block javascript_compress %}
<script type='text/javascript' src="{% static 'js/knockout/knockout.js' %}"></script>
<script type="text/javascript">
$(function() {
var RemoveTaskModel = function () {
var self = this;
self.tasks = ko.observableArray(window.TASKS);
self.remove_task = function(task) {
self.tasks.remove(task);
$("#json_blob").val(ko.toJSON(task));
}
}
ko.applyBinding(new RemoveTaskModel());
});
</script>
{% endblock %}
HTML
I would use reverse to do a reverse lookup of the URL for each task:
from django.core.urlresolvers import reverse
def TaskList(request, job_id):
job_tasks = Tasks.objects.filter(parent=job_id)
tasks_list = []
for task in job_tasks:
...
task_row['url'] = reverse('update_task', args=(),
kwargs={'task_id': task_id})
Then your observableArray should be able to bind the property from the JSON to the anchor tag. You might also note that in your code sample, your td is malformed:
<td a class="btn" data-bind="attr: {href: url}">Update</a></td>
it should be:
<td><a class="btn" data-bind="attr: {href: url}">Update</a></td>

Django: Multiple menus

My django based website will have 3 seperate menus. The items of first one are: contact, about, disclosures. The second one will have: terms and condtions, privacy policy, copyright. And items of main menu are: Home, link1, link2, link2.... The first two menus will have fixed items, and the items of last one may change. As I will be using forloop in the template, what is the best approach for creating those menus. The webpages will have just a title and content.
I like to use inclusion template tags for dynamic menus.
In my-app/templatetags/myappmenu.py, I have something like:
from django import template
register = template.Library()
#register.inclusion_tag('my-app/menu.html')
def myappmenu():
return [("label1", "link1"), ("label2", "link2")]
Then, in your template you can loop over the items and produce the menu in the format you desire (<p>, <ul>, etc.).
If you need to make items in the menu appear conditionally, you can add them to the list by checking permissions in the template tag; just pass the request or user object as argument to the template tag function.
You can stay DRY and just use django-menuware. It supports nested menus as well.
Install:
pip install django-menuware
# Add `menuware` to your settings.py**
# Add `MENUWARE_MENU` to your settings.py:**
Settings:
MENUWARE_MENU = {
"RIGHT_NAV_MENU": [
{ # Show `Login` to `unauthenticated` users ONLY
"name": "Login",
"url": "/login/",
"render_for_unauthenticated": True,
},
{ # Show `Logout` to `authenticated` users ONLY
"name": "Logout",
"url": "/logout/",
"render_for_authenticated": True,
},
{ # Show `Search` to all users
"name": "Search",
"url": "/search/",
"render_for_unauthenticated": True,
"render_for_authenticated": True,
},
],
"LEFT_NAV_MENU": [
{ # Show `Admin` to `superuser` ONLY
"name": "Admin",
"url": "admin:index", # Reversible
"render_for_authenticated": True,
"render_for_superuser": True,
},
{ # Show `Comment Admin` to `staff` users ONLY
"name": "Comment Admin",
"url": "/review/admin/",
"render_for_authenticated": True,
"render_for_staff": True,
},
]
Usage:
<!-- base.html -->
{% load menuware %}
<!DOCTYPE html>
<html>
<head><title>Django Menuware</title></head>
<body>
<!-- NAV BAR Start -->
{% get_menu "LEFT_NAV_MENU" as left_menu %}
<div style="float:left;">
{% for item in left_menu %}
<li class="{% if item.selected %} active {% endif %}">
{{item.name}}
</li>
{% endfor %}
</div>
{% get_menu "RIGHT_NAV_MENU" as right_menu %}
<div style="float:right;">
{% for item in right_menu %}
<li class="{% if item.selected %} active {% endif %}">
{{item.name}}
</li>
{% endfor %}
</div>
<!-- NAV BAR End -->
</body>
</html>
Minimally, you'd want to look at its Github README page before rolling your own.