Ansible - Combining Lists of Dictionaries - list

I have two lists of dictionaries. The first list of dict contains detailed information for all systems in an environment.
"cluster_detail": [
{
"env_name": "env_1",
"cluster_name": "cluster_1",
"primary": "server1",
"secondary": "server2,server3",
"Standby": "server4,server5",
"TieBreakers": "server6,server7"
},
{
"env_name": "env_1",
"cluster_name": "cluster_2",
"primary": "server11",
"secondary": "server12,server13",
"Standby": "server14,server15",
"TieBreakers": "server16,server17"
},
{
"env_name": "env_2",
"cluster_name": "cluster_7",
"primary": "server21",
"secondary": "server22,server23",
"Standby": "server24,server25",
"TieBreakers": "server26,server27"
},
{
"env_name": "env_2",
"cluster_name": "cluster_8",
"primary": "server31",
"secondary": "server32,server33",
"Standby": "server34,server35",
"TieBreakers": "server36,server37"
}
]
The second list of dict contains partial detail for some parts of the same environment.
"cluster_partial": [
{
"key": "env_1",
"value": [
"cluster_1",
"cluster_2",
"cluster_3",
"cluster_4"
]
},
{
"key": "env_2",
"value": [
"cluster_5",
"cluster_6",
"cluster_7",
"cluster_8"
]
}
]
I would like to add detail to "cluster_partial" by searching for its values (cluster_1, Cluster_2, etc...) in "Cluster_detail", and where there's a match, add in the standby or tiebreaker server names.
I've been going through https://docs.ansible.com/ansible/latest/playbook_guide/complex_data_manipulation.html and attempting various combinations of the examples given but am not having any success.
I've also looked at ansible - combine three lists of dictionaries but need to be more selective I think.
What I would like to end up with is
{
end_result:
[
{
"key": "env_1",
"value": [
{
"cluster_1": {
"tiebreaker": [
"server6",
"server7"
]
}
},
{
"cluster_2": {
"tiebreaker": [
"server16",
"server17"
]
}
},
"cluster_3",
"cluster_4"
]
},
{
"key": "env_2",
"value": [
"cluster_5",
"cluster_6",
{
"cluster_7": {
"tiebreaker": [
"server26",
"server27"
]
}
},
{
"cluster_8": {
"tiebreaker": [
"server36",
"server37"
]
}
}
]
}
]
}
Valid YAML
end_result:
- key: env_1
value:
- cluster_1:
tiebreaker: [server6, server7]
- cluster_2:
tiebreaker: [server16, server17]
- cluster_3
- cluster_4
- key: env_2
value:
- cluster_5
- cluster_6
- cluster_7:
tiebreaker: [server26, server27]
- cluster_8:
tiebreaker: [server36, server37]
I've tried the following
- name: Add tiebreaker Value to list of Dictionary
ansible.builtin.set_fact:
cluster_partial: |
{% for item in cluster_partialm %}
"tiebreaker": {{item}}
{% endfor %}
when: cluster_detail.cluster_name is defined and cluster_detail.cluster_name == cluster_partial.value`

It looks like cluster_partial is unnecessary for what you're doing; we can achieve something that almost matches your desired end state by using only the information in cluster_detail:
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
end_result: >-
{{
end_result|combine({
item.env_name: {
item.cluster_name: {
"tiebreaker": item.TieBreakers.split(',')
}
}
}, recursive=True)
}}
vars:
end_result: {}
loop: "{{ cluster_detail }}"
- debug:
var: end_result
Which produces:
TASK [debug] *******************************************************************
ok: [localhost] => {
"end_result": {
"env_1": {
"cluster_1": {
"tiebreaker": [
"server6",
"server7"
]
},
"cluster_2": {
"tiebreaker": [
"server16",
"server17"
]
}
},
"env_2": {
"cluster_7": {
"tiebreaker": [
"server26",
"server27"
]
},
"cluster_8": {
"tiebreaker": [
"server36",
"server37"
]
}
}
}
}

Create the dictionary of available clusters
cluster_name_tiebreaker: "{{ cluster_detail|
items2dict(key_name='cluster_name',
value_name='TieBreakers') }}"
gives
cluster_name_tiebreaker:
cluster_1: server6,server7
cluster_2: server16,server17
cluster_7: server26,server27
cluster_8: server36,server37
Use Jinja to create the structure
end_result_str: |
{% for e in cluster_partial %}
{{ e.key }}:
{% for cluster in e.value %}
{% if cluster in cluster_name_tiebreaker %}
{{ cluster }}:
tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
{% else %}
{{ cluster }}:
{% endif %}
{% endfor %}
{% endfor %}
end_result_dict: "{{ end_result_str|from_yaml }}"
gives the dictionary
end_result_dict:
env_1:
cluster_1:
tiebreaker: [server6, server7]
cluster_2:
tiebreaker: [server16, server17]
cluster_3: null
cluster_4: null
env_2:
cluster_5: null
cluster_6: null
cluster_7:
tiebreaker: [server26, server27]
cluster_8:
tiebreaker: [server36, server37]
Convert the dictionary to the list
end_result: "{{ end_result_dict|dict2items }}"
gives
end_result:
- key: env_1
value:
cluster_1:
tiebreaker: [server6, server7]
cluster_2:
tiebreaker: [server16, server17]
cluster_3: null
cluster_4: null
- key: env_2
value:
cluster_5: null
cluster_6: null
cluster_7:
tiebreaker: [server26, server27]
cluster_8:
tiebreaker: [server36, server37]
This is not exactly what you want. The attribute value is a dictionary. If you really want a list change the template
end_result_str: |
{% for e in cluster_partial %}
{{ e.key }}:
{% for cluster in e.value %}
{% if cluster in cluster_name_tiebreaker %}
- {{ cluster }}:
tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
{% else %}
- {{ cluster }}:
{% endif %}
{% endfor %}
{% endfor %}
gives
end_result:
- key: env_1
value:
- cluster_1:
tiebreaker: [server6, server7]
- cluster_2:
tiebreaker: [server16, server17]
- {cluster_3: null}
- {cluster_4: null}
- key: env_2
value:
- {cluster_5: null}
- {cluster_6: null}
- cluster_7:
tiebreaker: [server26, server27]
- cluster_8:
tiebreaker: [server36, server37]
Fit the template further if you really want to have various types of items in the lists. Remove the colon : to get strings instead of dictionaries with undefined (null) values
{% else %}
- {{ cluster }}
gives what you want
end_result:
- key: env_1
value:
- cluster_1:
tiebreaker: [server6, server7]
- cluster_2:
tiebreaker: [server16, server17]
- cluster_3
- cluster_4
- key: env_2
value:
- cluster_5
- cluster_6
- cluster_7:
tiebreaker: [server26, server27]
- cluster_8:
tiebreaker: [server36, server37]
Example of a complete playbook for testing
- hosts: localhost
vars:
cluster_detail:
- Standby: server4,server5
TieBreakers: server6,server7
cluster_name: cluster_1
env_name: env_1
primary: server1
secondary: server2,server3
- Standby: server14,server15
TieBreakers: server16,server17
cluster_name: cluster_2
env_name: env_1
primary: server11
secondary: server12,server13
- Standby: server24,server25
TieBreakers: server26,server27
cluster_name: cluster_7
env_name: env_2
primary: server21
secondary: server22,server23
- Standby: server34,server35
TieBreakers: server36,server37
cluster_name: cluster_8
env_name: env_2
primary: server31
secondary: server32,server33
cluster_partial:
- key: env_1
value: [cluster_1, cluster_2, cluster_3, cluster_4]
- key: env_2
value: [cluster_5, cluster_6, cluster_7, cluster_8]
cluster_name_tiebreaker: "{{ cluster_detail|
items2dict(key_name='cluster_name',
value_name='TieBreakers') }}"
end_result_str: |
{% for e in cluster_partial %}
{{ e.key }}:
{% for cluster in e.value %}
{% if cluster in cluster_name_tiebreaker %}
- {{ cluster }}:
tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
{% else %}
- {{ cluster }}
{% endif %}
{% endfor %}
{% endfor %}
end_result_dict: "{{ end_result_str|from_yaml }}"
end_result: "{{ end_result_dict|dict2items }}"
tasks:
- debug:
var: cluster_name_tiebreaker
- debug:
var: end_result_str
- debug:
var: end_result_dict|to_yaml
- debug:
var: end_result|to_yaml

Related

Converting a result to a list

I need your help to convert a result of a ServiceNow API request to a list.
The result shows like following:
"json": {
"result": {
"success": "True",
"entries": {
"matches": 119,
"records": [
{
"our_reference": "INV01567761"
},
{
"our_reference": "INV01134545"
},
{
"our_reference": "INV01345435"
},
{
"our_reference": "INV01345345"
}
]
What I need is just the value in the dict in a list form like:
["INV01567761","INV01567761","INV01567761","INV01567761"]
to get the list of the records field I choose:
- name: trying to get a list
set_fact:
incidentList: "{{ ticketresult.json.result.entries.records }}"
- debug: var=incidentList
output:
[
{
"our_reference": "INV01567761"
},
{
"our_reference": "INV01134545"
},
{
"our_reference": "INV01345435"
},
{
"our_reference": "INV01345345"
}
]
but then I'm lost....
I tried several things to extract the numbers from this dict, but getting always the error message because this dict is sitting in a list.
so I tried:
ticketresult.json.result.entries.records[0]
to get the dict in the list, but this returned only the first item in the dict:
{ "our_reference": "INV01567761" }
Is there an easy way to get out all the info in list-form?
The expression below
references: "{{ json.result.entries.records|
map(attribute='our_reference')|list }}"
gives the list
references:
- INV01567761
- INV01134545
- INV01345435
- INV01345345
I also found another way:
- set_fact:
list_of_tickets: "{{ list_of_tickets|default([]) + [item|string|regex_search('INV[0-9]{8}')] }}"
with_items:
- "{{ ticketresult.json.result.entries.records }}"

Livewire Saving checkbox values as they are checked or unchecked

I have been trying to save checkbox values as they are checked by the user. No save button for this part is needed.
This is a many-to-many relationship and therefore, it’s the relation table that gets updated with the new values.
The array returned by Livewire to updatedSelected() is the same as initial values from the database and that is where I am stuck.
The problem is when values exist in the DB, load the page, uncheck one or more and the array returned to updatedSelected() is the same as it was initially loaded on page load.
On checking a checkbox the values are updated properly as I get the full array of values as expected.
When unchecking, what I am expecting is only the values that are left checked to properly sync the model.
If you have any clues on this, your help would be appreciated!
pefumes.php
public $selected = [];
protected $rules = [
"selected.*" => "required"
];
public function updatedSelected( $values )
{
$this->validate();
if( $this->plant->id != null )
{
$this->plant->perfumes()->sync(
array_map( "intval", $values )
);
}
}
perfumes.blade.php
<div class="auto-grid-placement auto-grid-item">
#foreach( $perfumes as $perfume )
#php
$slug = slugafy( $perfume->name );
#endphp
<x-form.label for="{{ $slug }}-{{ $perfume->id }}" class="{{ $disabled }} checkbox-item px-2 py-2 inline-block">
<input wire:model="selected" type="checkbox" id="{{ $slug }}-{{ $perfume->id }}" class="" value="{{ $perfume->id }}" {{ $disabled }}> {{ $perfume->name }} - {{ $perfume->id }}
</x-form.label>
#endforeach
</div>
To further express the issue.
I was able to get through this by changing the wire:model="selected" to wire:click=”savePerfumes( $perfume->id)” by calling a method to filter and save the input values. This is the example that got me on the right track: https://www.youtube.com/watch?v=gNuWLHiRn-o
I must then conclude that the approach of calling the wire:model=”selectedPerfume”, the set array, on every checkbox is probably better, if not made for, single-use checkboxes and these are not checked from values from the DB on load, like most examples you see.
If one needs to check and uncheck checkboxes, this is my approach:
The view
<div class="auto-grid-placement auto-grid-item">
#foreach( $perfumes as $perfume )
#php
$slug = slugafy( $perfume->name );
$checked = ( in_array( $perfume->id, $selectedPerfumes->toArray() )? "checked='checked'" : "")
#endphp
<x-form.label for="{{ $slug }}-{{ $perfume->id }}" class="{{ $disabled }} checkbox-item px-2 py-2 inline-block">
<input wire:click="savePerfumes( {{ $perfume->id }} )" type="checkbox" id="{{ $slug }}-{{ $perfume->id }}" name="selectedPerfumes[]" {{ $checked }} class="" value="{{ $perfume->id }}" {{ $disabled }}> {{ $perfume->name }} - {{ $perfume->id }}
</x-form.label>
#endforeach
#error( "selectedPerfumes" )
<span class="error">{{ $message }}</span>
<!-- /.error -->
#enderror
</div>
The function to save
// Rules
protected $rules = [
"selectedPerfumes" => "array",
"selectedPerfumes.*" => "required|integer"
];
public function savePerfumes( $perfume_id )
{
$this->validate();
if( $this->selectedPerfumes->contains( $perfume_id ))
{
$this->selectedPerfumes = $this->selectedPerfumes->reject( function( $value ) use ($perfume_id) {
return $value == $perfume_id;
});
}else{
$this->selectedPerfumes->push( $perfume_id );
}
if( $this->plant->id != null )
{
$this->plant->perfumes()->sync(
$this->selectedPerfumes
);
}
}
try this
public $selected = [];
protected function rules()
{
return [
...
'selected' => 'required|array',
];
}
public function edit()
{
if ($this->validate()) {
$data = [
'name' => $this->name,
...
];
$plant = Plant::where('id', $this->plant->id)->update($data);
if (!empty($this->selected)) {
$selectedId = Perfume::whereIn('id', $this->selected)->pluck('id');
Plant::findOrFail($this->plant->id)->perfumes()->sync($selectedId);
}
}
}

maintain focus in django form when displaying errors

I have a home view with a form as footer to contact. Everything works fine, except that when there is some error in the form and the view redisplays everything with the corresponding errors, it doesn't keep the focus in the form (it shows the view as though you were entering for the first time, and so users can't see their form wasn't correct unless they scroll down).
I had thought of sending a message with JS, but it is really ugly.
I have tried with all the ways I have found to display errors ( raising them from different functions) but the problem persists.
Any idea?
Besides, even though I set required = False, the form keeps showing a message ("Complete this field") which I think comes from html, how can I remove it?
Thank you very much!
Code:
<form method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
<div class="fieldWrapper">
<strong>{{ form.mail.errors }}</strong>
<label for="{{ form.mail.id_for_label }}">Your email address:</label>
{{ form.mail }}
</div>
<div class="fieldWrapper">
{{ form.number.errors }}
<label for="{{ form.number.id_for_label }}">CC yourself?</label>
{{ form.number }}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
class ContactForm(forms.Form):
mail = forms.CharField(required=False, label=_('Mail'))
number = forms.CharField(required=False, label=_('Number'))
message = forms.CharField(min_length = 0, max_length=500, label=_('Message'))
def clean_mail(self):
mail = self.cleaned_data['mail']
if '#' not in mail:
raise ValidationError({"mail": _('The mail should contain "#"')})
return mail
Any idea?
I have an idea using jquery validation plugin, but I'm not sure if you can use it in your project. It is very simple to integrate with django error messages (error_messages). Let me explain you.
First: Function in JavaScript to check if you need to send csrf:
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
Second: Function in JavaScript if you need to show generic errors (non field errors) dynamically.
function showGenericErrors(errorMessages, id) {
len = errorMessages.length
if (len > 0) {
errorDiv = `
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>`;
for (var i = 0; i < len; i++) {
errorDiv += errorMessages[i];
errorDiv += (i === len-1) ? '' : '<br>';
}
errorDiv += '</div>';
$(errorDiv).insertAfter($('SOME_ELEMENT_OF_YOUR_PREFERENCE'));
}
}
Third: Django response with JsonResponse in your view (remember -> from django.http import Json response).
return JsonResponse({'messages': form.errors}, status=500, safe=False)
Fourth: Activating jquery validation plugin and handle your submit event with ajax to send data to Django for validation purpose.
form = $('form');
var $validator = $(form).validate({
rules: {
mail: {
required: true,
email: true
},
number: {
required: true,
minlength: 7,
maxlength: 15
},
message: {
required: true,
maxlength: 500
},
},
messages: {
mail: {
required: 'Mail required',
email: 'Invalid mail'
},
number: {
required: 'Number required',
minlength: 'Please enter at least {0} characters', //{0} will take the minlength specified in rules
maxlength: 'Please enter no more than {0} characters'
},
message: {
required: 'Message required',
maxlength: 'Please enter no more than {0} characters'
},
},
submitHandler: function(form) {
$('.alert').remove();
var formData = new FormData(form);
$.ajax({
url: 'contactus',
type: 'POST',
data: formData,
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader('X-CSRFToken', $('[name=csrfmiddlewaretoken]').val());
}
},
success: function(data) {
console.log(data);
},
error: function(xhr) {
console.log(xhr.responseJSON);
data = xhr.responseJSON;
if (data.messages) {
genericErrors = [];
fieldErrors = {};
for (var i in data.messages) {
if (i === '__all__') {
genericErrors = data.messages[i];
} else {
fieldErrors[i] = data.messages[i][0];
}
}
$validator.showErrors(fieldErrors);
showGenericErrors(genericErrors, id);
}
},
cache: false,
contentType: false,
processData: false
});
},
errorElement: "em",
errorPlacement: function(error, element) {
error.addClass("help-block");
if (element.prop("type") === "checkbox") {
error.insertAfter(element.parent("label"));
} else {
error.insertAfter(element);
}
},
highlight: function(element, errorClass, validClass) {
$(element).parents(".fieldWrapper").addClass("has-error").removeClass("has-success");
},
unhighlight: function(element, errorClass, validClass) {
$(element).parents(".fieldWrapper").addClass("has-success").removeClass("has-error");
}
});

django leaflet_map how to add geojson layer on demand

I have a geodjango project using django-leaflet. A base map is displayed in the body of the html file using {% leaflet_map "mapid" %} . No other initialization is needed for the base map to display. Also, there is a choice box of polygon features that user can select. Upon selection the selected feature should display on top of the base map. As shown, the selected feature is the success response of a post ajax call. I get the correct data, however, I don't know how to display it on the map.
<html>
<head>
{% load leaflet_tags %}
{% leaflet_css %}
{% leaflet_js %}
<style>
#mapid {
width: 870px;
height: 530px;
}
</style>
</head>
<body>
<div> {% leaflet_map "mapid" %} </div>
<form id="aoi-form" method="post" action="" >
{% csrf_token %}
<select id="aoi-choice" name="modelarea_sel_name" >
{% for aoi in modelarea.aois.field.choices %}
<option>{{aoi|title}}</option>
{% endfor %}
</select>
</form>
<script type="text/javascript">
$(document).ready(function() {
// sets the csrf token for all the ajax calls
setCSRFToken();
function displayModelArea(data) {
window.addEventListener("map:init", function(event) {
var map = event.detail.map;
L.geoJson(data, {
onEachFeature: function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.name);
}
}).addTo(map);
});
}
$('#aoi-choice').on('change', function(e) {
var selecteditem = $(this).find("option:selected").val();
$.ajax({
url: $("#aoi-form").attr("action"),
type: $("#aoi-form").attr("method"),
data: { "aoi-choice": selecteditem },
success: function(response) {
alert(response);
<How to add this geojson to the map in success?>
<something like:>
displayModelArea(response) // this is not working
},
error : function(xhr,errmsg,err) {}
}); // end of ajax
}); // end of on change
}); // end of document
</script>
</body>
</html>
views.py
def rsmgui_home(request):
template_name = 'rsmgui_nav.html'
if request.POST.get("aoi-choice"):
polydata = serialize('geojson', ModelArea.objects.filter(name=request.POST.get("aoi-choice")))
return JsonResponse(polydata, safe=False)
For those interested in the solution, I must say, my original code contained two mistakes:
1) Had to return HttpResponse instead of JsonResponse from the views.py.
2) Had to initialize and get handle of the map (i.e. mymap) outside of the $(document).ready. Although, I am sure I should be able to do the same with jquery, But I did not know how!
FYI - the data format returned from views was somethings like this:
{"type": "FeatureCollection", "features": [{"type": "Feature",
"properties": {
"objectid": 3235,
"name": "WCU-L38E-2",
"shape_area": 5178189.95436,
"shape_len": 20962.4168727,
"model": "rsmgui.modelarea"
},
"id": 3,
"geometry": {
"type": "MultiPolygon",
"coordinates":
[[[[-80.4431746089703, 26.1496150525031], [-80.4438712320284, 26.1494779568429], [-80.4438714835869, 26.1494791485814],
[-80.4438767327393, 26.1495038898062], [-80.443876985872, 26.1495050844166], [-80.4439596517546, 26.1498945523],
[-80.4439599048889, 26.1498957469102], [-80.4445181371852, 26.1525257445301], [-80.444518396688, 26.1525269377307],
[-80.4454063419315, 26.1566079785707], [-80.4454066030393, 26.1566091717754], [-80.4454367956768, 26.1567473825391],
[-80.4454269367566, 26.1568310621773], [-80.4454019935245, 26.1569010164227], [-80.4451487080981, 26.1572161211586],
[-80.4451198504533, 26.1572690055153], [-80.4451124234237, 26.1572826141087], [-80.4450987418606, 26.1573731140696],
[-80.4451097451673, 26.1574483323541], [-80.4451099518947, 26.1574492731537], [-80.4454045318579, 26.1587738519124],
[-80.4454291560893, 26.1588281989547], [-80.4454726925214, 26.158873231686], [-80.4454745412333, 26.1588751446035],
[-80.4457224526486, 26.1590556968988], [-80.4459825369665, 26.1592924192246], [-80.4460113732483, 26.1593318182711],
[-80.446029694147, 26.1594029793378], [-80.4460299489157, 26.1594041725167], [-80.4461426842445, 26.1599298599992],
[-80.4461429406032, 26.159931053184], [-80.4461632993508, 26.1600259843658], [-80.4461635557098, 26.1600271775505],
[-80.4461662854139, 26.1600399110449], [-80.446166541773, 26.1600411042296], [-80.4461669349025, 26.1600429241313],
[-80.4461698308018, 26.1600564420782], [-80.4461703745794, 26.1600589761589], [-80.4461706309386, 26.1600601693436],
[-80.4461724673901, 26.1600687195423], [-80.446172720567, 26.1600699141476], [-80.4461890554485, 26.1601460828104],
[-80.4461892651751, 26.1601470637419], [-80.4461894764895, 26.1601480446796], [-80.4464055948207, 26.1611557705659],
[-80.4464058511845, 26.1611569637501], [-80.4465182479751, 26.1616803740569], [-80.446518504341, 26.1616815672408],
[-80.4467438353217, 26.1627308699823], [-80.4467440932799, 26.1627320631719], [-80.4471482872166, 26.1646036789173],
[-80.4471485483585, 26.1646048721181], [-80.4483626433148, 26.1701518069936], [-80.4483628854181, 26.1701530015516],
[-80.4483814524309, 26.1702450510074], [-80.4483896526105, 26.1703196621089], [-80.44837083869, 26.1703813120244],
[-80.4483349119681, 26.1704454670367], [-80.448238820436, 26.1705762562447], [-80.4480865737154, 26.1708302726741],
[-80.4480562687647, 26.1709111657153], [-80.4480474043405, 26.1709779965095], [-80.4480570552683, 26.1710631695728],
[-80.4480726187489, 26.1711178954947], [-80.4480728735615, 26.1711190886688], [-80.448600444514, 26.1735684214603],
[-80.4486318283615, 26.1736785332924], [-80.4486793668728, 26.1737445068659], [-80.4492087688318, 26.1741548565954],
[-80.4492476879068, 26.1742313275962], [-80.4492734967374, 26.1743198523785], [-80.4492737277215, 26.1743210511907],
[-80.4493637707618, 26.174788553371], [-80.4494647807674, 26.1752251203668], [-80.4494649710993, 26.1752261843278],
[-80.4495030325796, 26.1754400354424], [-80.4495075156305, 26.1755698630249], [-80.4494919585678, 26.1757079020217],
[-80.4494654601658, 26.1758137385183], [-80.4494247803613, 26.1759047014177], [-80.4493654321733, 26.1760018949102],
[-80.449291361239, 26.1760990334811], [-80.449224827617, 26.1761585592545], [-80.4490944858737, 26.176220056103],
[-80.448817683956, 26.176319577968], [-80.4486133770828, 26.176391343208], [-80.448294685515, 26.1764133756087],
[-80.4482742214126, 26.1764147902816], [-80.4482629422319, 26.1764173105563], [-80.4482608358316, 26.1764177810802],
[-80.4481701381423, 26.1764380517997], [-80.4480857500865, 26.1764548904322], [-80.4480278075929, 26.1764455275992],
[-80.4479938505702, 26.1764255169321], [-80.4447866204566, 26.1616803278906], [-80.4427502472453, 26.1523162458952],
[-80.4425042672057, 26.1511130652601], [-80.4424324383669, 26.1508789484218], [-80.4422838184404, 26.1506638659222],
[-80.442263580685, 26.150586872045], [-80.4422584377825, 26.1505622200219], [-80.4422537121117, 26.1505393091941],
[-80.4422484417484, 26.1505144173773], [-80.4422447794031, 26.1504939380542], [-80.4422409109672, 26.1504737273243],
[-80.4422344502975, 26.1504440606653], [-80.4422140566669, 26.1500502089701], [-80.4421980585152, 26.1498452491223],
[-80.4421989899204, 26.149844702483], [-80.4422962649397, 26.149811382303], [-80.4423241174424, 26.1498018149787],
[-80.442339856439, 26.1497964493973], [-80.4423577440382, 26.1497903226474], [-80.4423887913014, 26.1497796872474],
[-80.4424405282263, 26.1497619686973], [-80.4424639436836, 26.1497539446344], [-80.4424890564009, 26.1497459185392],
[-80.4426580984819, 26.1496918891336], [-80.4431746089703, 26.1496150525031]]]]}}],
"crs": {
"type": "link",
"properties": {
"href": "http://spatialreference.org/ref/epsg/4326/",
"type": "proj4"
}
}
};
Corrected code:
<html>
<head>
{% load leaflet_tags %}
{% leaflet_css %}
{% leaflet_js %}
<style>
#mapid {
width: 870px;
height: 530px;
}
</style>
</head>
<body>
<div>
{% leaflet_map "mapid" %}
</div>
<form id="aoi-form" method="post" action="" >
{% csrf_token %}
<select id="aoi-choice" name="modelarea_sel_name" >
{% for aoi in modelarea.aois.field.choices %}
<option>{{aoi|title}}</option>
{% endfor %}
</select>
</form>
<script type="text/javascript">
var mymap;
window.addEventListener("map:init", function (event) {
mymap = event.detail.map;
}, false);
$(document).ready(function() {
function displayModelArea(map, data) {
L.marker([26.92, -81.5]).addTo(mymap);
L.geoJSON(data).addTo(mymap);
}
$('#aoi-choice').on('change', function(e) {
var selecteditem = $(this).find("option:selected").val();
$.ajax({
url: $("#aoi-form").attr("action"),
type: $("#aoi-form").attr("method"),
data: { "aoi-choice": selecteditem },
success: function(response) {
displayModelArea(mymap, response)
},
error : function(xhr,errmsg,err) {}
}); // end of ajax
}); // end of on change
}); // end of document
</script>
</body>
</html>
views.py
def rsmgui_home(request):
template_name = 'rsmgui_nav.html'
if request.POST.get("aoi-choice"):
polydata = serialize('geojson', ModelArea.objects.filter(name=request.POST.get("aoi-choice")))
return HttpResponse(polydata, content_type='json')

Create pie chart with HighCharts in Django error - Blank page

I'm trying to create a pie chart with highcharts with Django (with Mac Maverick), but I just get a blank page. Is there some error in my code below?
My data consists of a dictionary with 2 keys (body and id__count), the loop inside it works perfectly in body tag. But it's not working in script tag.
<script>
function numberWithCommas(x) {
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
$(function () {
var chart;
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {
renderTo: 'categoryPieChart'
},title: {
text: 'Category'
},tooltip: {
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ numberWithCommas(this.y.toFixed(2));
}
},exporting: {
enabled: false
},plotOptions: {
pie: {
dataLabels: {
enabled: true,
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ Math.round(this.percentage) +' %';
}
}
}
},series: [{
type: 'pie',
data: [
{% for cat in responses_pie %}
[
'{{ cat.body }}',
{% if cat.id__count > 0 %}
{{ cat.id__count }}
{% else %}
0
{% endif %}
],
{% endfor %}
]
}]
});
});
});
});
</script>
This is the view source on Chrome in the script tag:
function numberWithCommas(x) {
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
$(function () {
var chart;
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {
renderTo: 'categoryPieChart'
},title: {
text: 'Category'
},tooltip: {
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ numberWithCommas(this.y.toFixed(2));
}
},exporting: {
enabled: false
},plotOptions: {
pie: {
dataLabels: {
enabled: true,
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ Math.round(this.percentage) +' %';
}
}
}
},series: [{
type: 'pie',
data: [
[
'apple',
2
],
]
}]
});
});
});
});
And my view (views.py)
def piechart(request):
responses_pie = AnswerRadio.objects.values("body").annotate(Count("id"))
return render(request, 'chart_code_v2.html', {'responses_pie': responses_pie})
You have an extra }); at the end (one from Highcharts.Chart({, one from $(document).ready(function() {, one from $(function () { and one too much). This should be visible as a syntax error in your javascript console.
Also, calling $(document).ready(fn) is superfluous, because $(fn), which you use on the function around it, is a shorthand for $(document).ready(fn).
If you plan to support old Internet Explorer versions, you will also need to remove the last comma from your data, for example like this:
{% for cat in responses_pie %}
[
'{{ cat.body }}',
{% if cat.id__count > 0 %}
{{ cat.id__count }}
{% else %}
0
{% endif %}
]{% if not forloop.last %},{% endif %}
{% endfor %}
Generally, it is better to serialize data in the view via json.dumps and pass the string to the template, as it is less error prone. For example:
import json
def piechart(request):
responses_pie = AnswerRadio.objects.values("body").annotate(Count("id"))
res = []
for cat in responses_pie:
res.append([
cat.body,
cat.id__count,
# ... more stuff
])
return render(request, 'chart_code_v2.html', {'responses_pie_json': json.dumps(res)})
then just use data: {{ responses_pie_json|safe }} in your template. Be aware that some Python objects (datetime, Decimal, ...) need some extra work for JSON serialization.