I have created a changelist_view for displaying a chart.js visual in the Django admin.
I am not getting any errors, the chart outline is visible, but the data is not. Not sure what I'm missing. Info below:
admin.py model:
class MachineFaultAdmin(admin.ModelAdmin):
readonly_fields = [
'event_id',
'fault_type_id',
'position',
]
list_display = [
'event_id',
'fault_type_id',
'position',
]
def changelist_view(self, request, extra_context=None):
# Aggregate Faults
chart_data = (
MachineFault.objects.all()
.values('position')
.annotate(total=Count('fault_type_id'))
.order_by('total')
.filter(position__gte=10)
)
#Serialize and attach the chart data to the template context
as_json = json.dumps(list(chart_data), cls=DjangoJSONEncoder)
extra_context = extra_context or {"chart_data": as_json}
#Call the superclass changelist_view to render the page
return super().changelist_view(request, extra_context=extra_context)
def has_add_permission(self, request):
# Nobody is allowed to add
return False
def has_delete_permission(self, request, obj=None):
# nobody is allowed to delete
return False
# suit_classes = 'suit-tab suit-tab-faults'
empty_value_display = ''
list_filter = ('fault_type',)
search_fields = ('position',)
changelist_view html (admin override file)
<!--# machines/templates/admin/machines/machinefault/change_list.html-->
{% extends "admin/change_list.html" %}
{% load static %}
<!-- Override extrahead to add Chart.js -->
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const ctx = document.getElementById('myChart').getContext('2d');
// Sample data
const chartData = {{ chart_data | safe }};
// Parse the dates to JS
chartData.forEach((d) => {
d.x = new Date(d.date);
});
// Render the chart
const chart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [
{
label: 'Breaks ',
data: chartData,
backgroundColor: 'rgba(220,20,20,0.5)',
},
],
},
options: {
responsive: true,
scales: {
xAxes: [
{
type: 'time',
time: {
unit: 'day',
round: 'day',
displayFormats: {
day: 'MMM D',
},
},
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});
});
</script>
{% endblock %}
{% block content %}
<!-- Render our chart -->
<div style="width: 80%;">
<canvas style="margin-bottom: 5px; width: 50%; height: 15%;" id="myChart"></canvas>
</div>
<button id="reload" style="margin: 1rem 0">Reload chart data</button>
<!-- Render the rest of the ChangeList view -->
{{ block.super }}
{% endblock %}
But my chart is still blank - I have no errors.
Using latest Django, Python3.7
UPDATE: 2/14/2020
Can't be sure that this is the cause of your issue without seeing the HTML, but the way you are passing data to your chart JS is unsafe - don't do this:
const chartData = {{ chart_data | safe }};
It's very likely you are ending up with invalid JS as a result of this, because the output is not properly escaped. Instead, use the json_script filter to safely render your object, and then read this in JS. Something like this:
{{ chart_data|json_script:"chart-data" }}
<script>
const chartData = JSON.parse(document.getElementById("chart-data").textContent);
// initialise the chart as you currently do
</script>
Note - you need to stop encoding the data as JSON in your view - just pass it the original list which this filter will encode safely for you.
If this doesn't fix it then it's likely that the data structure itself is not what the chart library is expecting - perhaps if you post a sample of what chartData looks like we can see whether that looks right.
Related
Hi Guys I have inspired from this Fiddle example to try to create a similar multi axis line chart in my django project.
I have in my views :
class dashboard(TemplateView):
template_name = 'users/dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['qs'] = DummyData.objects.all()
data = DummyData.objects.all()
years=[]
for i in range(data.count()) :
if data[i].year not in years :
years.append(data[i].year)
context['years'] = years
return context
in in my dashboard.html :
{% extends 'users/base.html' %} {% load static %} {% block content %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- semantic UI -->
<link rel="stylesheet" type='text/css' href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.14/semantic.min.css">
<!--Chart js-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" integrity="sha256-Uv9BNBucvCPipKQ2NS9wYpJmi8DTOEfTA/nH2aoJALw=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" integrity="sha256-aa0xaJgmK/X74WM224KMQeNQC2xYKwlAt08oZqjeF0E=" crossorigin="anonymous" />
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$(document).ready(function (){
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [{% for year in years %} '{{year}}', {% endfor %}],
datasets: [{% for item in qs %}
{
label: '{{item.site}}',
yAxisID: '{{item.site}}',
data: [100, 96, 84, 76, 69] , {% endfor %}
]
},
options: {
scales: {
yAxes: [{
id: 'A',
type: 'linear',
position: 'left',
}, {
id: 'B',
type: 'linear',
position: 'right',
ticks: {
max: 1,
min: 0
}
}]
}
}
});
});
</script>
<canvas id="myChart" width="400" height="100"></canvas>
{% endblock content %}
When I copied the fiddle example it showed correctly in my dahsboard.html as follows :
But When I tried to change the datasets in the code as presented in my dashboard.html nothing shows up, labels are updated okay but this is what makes my chart not work :
datasets: [{% for item in qs %}
{
label: '{{item.site}}',
yAxisID: '{{item.site}}',
data: {{item.turn_over}}' , {% endfor %}
]
I am sure this is not how it is supposed to be done, I'm a beginner at chart.js , what I want to do is load how many sites I have in my Dummydata table and show their turn over
Thank you
Done It was a comma positioning mistake, I checked the console and the problem was that when my if condition was not being checked instead of skipping, it was adding an empty value, meaning that I was getting for example 1, , , , 2 , , , 3 instead of 1,2,3 and the chart was rendering the empty cells, anyway here is the how I was able to fix it for data :
data: {
labels: [{% for year in years %} '{{year}}', {% endfor %}],
datasets: [
{% for site in sites %}
{
label: '{{site.site}}',
yAxisID: 'y',
borderColor: '{{site.color}}',
backgroundColor: 'transparent',
data: [ {% for item in qs %}
{% if item.site == site.site %}
{{item.turn_over}} ,{% endif %} {% endfor %}
]
}, {% endfor %}
]
}
I wanted to create a filter to get some value range. there I want to get 2 numbers in to parameter_name for that i added 2 number field. like bellow image.
If I use only one field, value of that field can be get. as value = self.value()
<input type="number" step="0.1" max="0.9" min="0.1" id="IsWithinRange" name="IsWithinRange">
But i need these 2 values so added another field, then non of their value can get. self.value() always is Non.
now I cannot figure out how can I pass this value to my filter. can anyone direct me to the correct path.
This may be not the exact answer. but using this you might be able to fulfill the requirement.
※If there are more than one input fields the Django filter will not work properly. Therefore have to use one input field per a filter.
(Use Jquery slider to select range rather than using two input fields)
Python class
class IsWithinRangeFilter(admin.SimpleListFilter):
title = 'Title'
parameter_name = 'scoreRange'
template = '[path]/input_filter.html'
def lookups(self, request, model_admin):
return (
('Yes', 'Yes'),
)
def queryset(self, request, queryset):
value = self.value()
try:
if value:
#do whatever
except Exception as e:
logger.info('#####ERROR{}' .format(e))
return queryset
call the filter class
list_filter = (IsWithinRangeFilter,)
input_filter.html
{% load i18n %}
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" />
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
var lowerRange = 5;
var upperRange = 8;
$( function() {
$( "#slider-range" ).slider({
range: true,
min: 0,
max: 10,
values: [ lowerRange, upperRange ],
slide: function( event, ui ) {
if(ui.values[ 0 ] === ui.values[ 1 ])
{
return false;
}
$( "#scoreRange" ).val( ui.values[ 0 ]/10 + " - " + ui.values[ 1 ]/10 );
}
});
$( "#scoreRange" ).val( $( "#slider-range" ).slider( "values", 0 ) / 10 +
" - " + $( "#slider-range" ).slider( "values", 1 ) / 10 );
} );
</script>
{% block content %}
<h3>{% Any Title %}</h3>
<ul>
<li>
<form>
<p>
<label >Any Description</label>
<div id="slider-range"></div>
<label style="font-weight: bold;">Filter:</label><input type="submit" id="scoreRange" name="scoreRange" style="margin:5px; text-align:center; background-color:#79aec8; cursor:pointer; border:0; color:#f7f5f3; font-weight:bold;">
</p>
</form>
</li>
</ul>
{% endblock %}
hope this will help!!!
I am using django, react and react-codemirror2 in my project where a user can input SQL code. I am running into an issue where only part of the text from textarea exists in request.POST. I am finding that mostly just part of the last line of the textarea is sent. I am baffled.
React component
import React from 'react'
import {UnControlled as CodeMirror} from 'react-codemirror2'
import DjangoCSRFToken from 'django-react-csrftoken'
class SQLForm extends React.Component{
componentDidMount() {
let textarea = document.getElementsByTagName('textarea')[0]
textarea.setAttribute('name', 'sql');
}
render(){
return(
<div>
<form action="" method="POST">
<DjangoCSRFToken/>
<CodeMirror
options={{
mode: 'text/x-mssql',
theme: 'material',
lineNumbers: true
}}
onChange={(editor, data, value) => {
}}
/>
<br/>
<button type="submit">Submit</button>
</form>
</div>
)
}
}
export default SQLForm
super simple django view (just to see what is submitted)
def index(request):
if 'sql' in request.POST:
print(request.POST['sql'])
return render(request, 'react.html', {})
else:
return render(request, 'react.html', {})
for example, in the text area, if type in the following
SELECT *
FROM whatever
WHERE something=2
print(request.POST['sql']) shows the following at the console
=2
And for completeness, this is the textarea tag when the page loads
<textarea autocorrect="off" autocapitalize="off" spellcheck="false" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;" tabindex="0" name="sql"></textarea>
got this to work by not worrying about CodeMirror being in the form. Instead I used state to capture the last value from the CodeMirror onChange function and then assigned that to the value attribute of the button. Switched the name attribute being in the textarea to being in the button itself.
import React from 'react'
import {UnControlled as CodeMirror} from 'react-codemirror2'
import DjangoCSRFToken from 'django-react-csrftoken'
class SQLForm extends React.Component{
constructor(props){
super(props)
this.updateText = this.updateText.bind(this);
this.state = {
sql_text: ''
}
}
updateText(value){
this.setState((prevState)=>({sql_text:value}))
}
render(){
return(
<div>
<CodeMirror
options={{
mode: 'text/x-mssql',
theme: 'material',
lineNumbers: true
}}
onChange={(editor, data, value) => {
{this.updateText(value)}
}}
/>
<form action="" method="POST">
<DjangoCSRFToken/>
<button type="submit" value={this.state.sql_text} name="sql">Submit</button>
</form>
</div>
)
}
}
export default SQLForm
I was stuck in passing dict's data to highchart using django
here's my views.py code. My thought is trying to pass the database's data to dictionary data , then pass its to user_chart.html's highchairs
def user_chart(request):
user_data = User_Data.objects.filter(user=request.user.username)
data = {'words':[], 'click_times':[]}
for i in user_data:
data['words'].append(i.word)
data['click_times'].append(i.click_times)
xAxis = {"title": {"text": 'ss'}, 'categories': data['words']}
yAxis = {"title": {'text': 'fdfd'}}
series = [
{"name": 'dfdf', "data": data['click_times']}
]
content = {'xAxis': xAxis, 'yAxis': yAxis, 'series': series}
return render(request, 'yigu/user_chart.html', content)
user_chart.html's code. I want to achieve a goal that highchart receives the data then present its as chart.
{% extends 'yigu/base_charts.html' %}
{% block body_block %}
<div class="container">
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-1"></div>
<div class="col-md-1"></div>
<div class="col-md-1">
<div id="chart1" style="width:450px">
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
<script>
var xAxis = {{ xAxis|safe }}
var yAxis = {{ yAxis|safe }}
var series = {{ series|safe }}
</script>
<script>
$(document).ready(function(){
$("#chart1").highcharts({
chart: {
type: 'column'
},
title: {
text: '搜索频率'
},
xAxis: xAxis,
yAxis: yAxis,
series: series
});
});
</script>
{% endblock %}
But i just got the blank response, the chart didn't show up. Anyone could give me a hand?
After reading http://blogs.law.harvard.edu/rprasad/2011/08/30/highcharts-django-admin/ I noticed categories: [ '{{ categories|join:"','" }}'] And i thought that maybe i should try this type. Then i change my template into something like below
<script>
$(document).ready(function(){
$("#chart1").highcharts({
chart: {
type: 'column'
},
title: {
text: '搜索频率'
},
xAxis: {
title:{
text: '词条',
},
categories: [ '{{ data.words|join:"','"}}']
},
yAxis: {
title:{
text: '次数'
}
},
series:[{
name:'搜索次数',
data:[{{ data.click_times|join:"," }}]
}]
});
});
</script>
Then everything went right. I was so surprised. I hope the solution would help someone.
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.