I want to iterate over an object I return from django view to create dynamically columns in a table (each column is a field in a model and I want to be able to switch the model without changing the template)
In the html body part of the template the same exact code worked fine, in the script section it's not.
My guess is the zip object pass as iterator that can consumed only once? if so how do I make django send a normal list object?
This is my view:
def HelperColumnesFieldsAndNames(data):
columns = { '<ManyToOneRel: client.paymentdata>':["unimp_pay","unimplemented"],
'<ManyToOneRel: client.subscribtiondata>':["subscribtiondata","unimplemented"],
'<ManyToOneRel: client.calldata>':["calldata","unimplemented"],
'<ManyToOneRel: client.extracommunicationdata>':["extracommunicationdata","unimplemented"],
'client.Client.creation_date':["creation_date","Creation Date"],
'client.Client.first_name':["first_name","First Name"],
'client.Client.last_name':["last_name","Last Name"],
'client.Client.address':["address","Address"],
'client.Client.city':["city","City"],
'client.Client.phone_number':["phone_number","Main Phone"],
'client.Client.creator_of_user':["creator_of_user","Client Creator"],
'client.Client.status':["status","Status"],
'client.Client.status_change_time':["status_change_time","Last Time Status Changed"],
'client.Client.allocated':["allocated","Currently Allocated To Talpan"],
'client.Client.group':["group","Owner Group"],
'client.Client.tags':["tags","Tags"],
'client.Client.tagged_items':["tagged_items","Tagged Item"],
}
column_name = []
column_field = []
for field in data:
field = str(field)
if field in columns.keys():
column_field.append(columns[field][0])
column_name.append(columns[field][1])
return zip(column_name, column_field)
# view function
def AllClientts(request):
user_groups = []
for groups in request.user.groups.all():
if groups.name != MANGER_GROUP_NAME:
user_groups.append(groups)
# get all object that belongs to the requested user groups
tableData = Client.objects.filter(group__in=user_groups)
return render(request, "client/clients.html", {"objects": tableData, "columns_name":HelperColumnesFieldsAndNames(Client._meta.get_fields()) })
Working example non in the script section
{% block content %}
<div class="d-flex pt-5 pb-2">
<!-- Need to be set to colums initials names using django -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Show
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" id="swichable_column">
<!--reate fileds name and Showable names -->
{% for name in columns_name %}
<div class="checkbox">
<label>
<input type="checkbox" checked="checked" class="chackable_name" value="{{ name.1 }}">{{ name.0 }}</label>
</div>
{% endfor %}
</div>
</div>
</div>
<div id="example-table"></div>
{% endblock content %}
Script section in my tamplet - not working even do it's the same code
<script>
//define some sample data
var tabledata = [
{id:1, first_name:"Oli Bob", age:"12", col:"red", dob:""},
{id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"},
{id:3, name:"Christine Lobowski the gratest and latest", age:"42", col:"green", dob:"22/05/1982"},
{id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"},
{id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"},
];
$(function() {
var table = new Tabulator("#example-table", {
height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
data:tabledata, //assign data to table
layout:"fitColumns",
pagination:"local",
paginationSize:6,
paginationSizeSelector:[3, 6, 8, 10],
columns:[ //Define Table Columns
{% for name in columns_name %}
{title:"{{ name.0 }}", field:"{{ name.1 }}", align:"center", cellClick:function(e, cell){alert("cell clicked - " + cell.getValue())}},
{% endfor %}
]});
</script>
Apparently if I return zip object directly it's itearable and can be consume once only.
I replace zip() with list(zip()) and it's solved that issue
Related
I am using a Django List View, and I am trying to iterate through multiple objects and display percentage values in a Chart.JS gauge.
However, although I am iterating the names of the gauge id's by using a for loop counter, I am only ever getting the first iteration of the chart.js object rendering on my screen. My initial thoughts are that similar to how I am dynamically creating new canvas ids for the chart.js objects, I should be doing a similar thing for the variable I am trying to pass into the chart object e.g. reduction overall, but I am not having any luck. Your feedback is welcome.
Views.py
class PerformanceDetailView(ListView):
template_name = 'performance_detail.html'
model = Performance
context_object_name = 'performance'
def get_queryset(self, **kwargs):
code = self.kwargs.get('code')
return Performance.objects.filter(code=code)
Performance_detail.html
<section class="projects pt-4 col-lg-12">
{% for item in performance %}
<div class="container-fluid pt-2 col-lg-12">
<!-- Project--><div class="project" >
<div class="row bg-white has-shadow" style="height: 14rem">
<div class="left-col col-lg-2 d-flex align-items-center justify-content-between">
<div class="project-title d-flex align-items-center">
<div class="has-shadow"><img src="{% static 'img/avatar-2.jpg' %}" alt="..." class="img-fluid"></div>
</div>
</div>
<div class="right-col col-lg-2 align-items-center vertical-center">
<div class="text">
<h3 class="h2 pt-2">{{item.brand}} {{item.style}}</h3>
<p class="text-muted">{{item.package_type| capfirst}} package, round {{item.testing_round}}</p>
<p class="text-muted">Item size: {{item.size}}</p>
</div>
</div>
<div class="right-col col-lg-8 align-items-center vertical-center">
<div class="row mt-5">
<div class="col-md-3">
<div class="gauge-container">
<canvas id="gauge1-{{ forloop.counter }}" class="gauge-canvas"></canvas><span id="gauge1Value-{{ forloop.counter }}" class="gauge-label"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{ item.reduction_overall|json_script:"reduction_overall" }}
{{ for_loop_counter|json_script:"for_loop_counter" }}
{% endfor %}
</section>
{% endblock content %}
Block JS within package_detail.html
<!-- Gauge.js-->
<script src="{% static 'vendor/gaugeJS/gauge.min.js' %}"></script>
<script>
$(function () {
var reduction_overall = JSON.parse(document.getElementById('reduction_overall').textContent);
var for_loop_counter = JSON.parse(document.getElementById('for_loop_counter').textContent);
// Gauges
var gauge1 = document.getElementById('gauge1-'+for_loop_counter);
opts.colorStop = "#864DD9";
var gaugeObject1 = new Donut(gauge1).setOptions(opts);
gaugeObject1.maxValue = 100; // set max gauge value
gaugeObject1.setMinValue(0); // set min value
gaugeObject1.set(reduction_overall); // set actual value
gaugeObject1.setTextField(document.getElementById("gauge1Value-" + for_loop_counter));
});
</script>
{% endblock js %}
This was solved by including the JavaScript in the main block content and dynamically naming the gauge elements themselves.
var func_name = "container-{{forloop.counter}}";
func_name = new Donut(gauge1).setOptions(opts);
I posted a full example to a similar problem here Chart JS and Django loop
I am doing filter using GET method and I would like to be able to remove only specific filters, how do I do that?
Now I have a form with token fields so I get id's of countries and towns:
<form id="search" method="GET" action="{% url 'selection'%}">
<div class="bg-grey text-center">
<input type="text" class="form-control" id="tokenfield1" name="country">
</div>
{% if filteredcountries %}
<table class="table table-list">
{% for c in filteredcountries %}
<tr>
<td><button type="button" class="btn btn-xs btn-danger" data-remove="{{c.id}}"><i class="fas fa-times"></i></button></td>
<td>{{c.country_name`}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
<div class="bg-grey text-center">
<input type="text" class="form-control" id="tokenfield2" name="town">
</div>
{% if filteredtowns %}
<table class="table table-list">
{% for t in filteredtowns %}
<tr>
<td><button type="button" class="btn btn-xs btn-danger" data-remove="{{t.id}}"><i class="fas fa-times"></i></button></td>
<td>{{t.town_name}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</form>
views.py:
def hotel_view(request):
countries = request.GET.get('country')
if not countries:
countries = []
else:
countries = countries.split(', ')
towns = request.GET.get('town')
if not towns:
towns = []
else:
towns = towns.split(', ')
results = Hotel.objects.filter(Q(town_id__in = towns) | Q(country_id__in = countries))
context = {
'filteredcountries': countries,
'filteredtowns': towns,
'hotels': results
}
return render(request, 'myapp/hotels.html', context)
Now I want to click the button and remove the specific town or country from the GET and remain the others. Can you lead me to the solution?
example URL before button click:
..myapp/hotels?country=**2**%2C+**4**&town=2
url after button click with country id 2:
..myapp/hotels?country=**4**&town=**2**
or after button click with town id 2:
..myapp/hotels?country=**2**%2C+**4**&town=
This is a solution around using javascript and JQuery. The basic approach is as follows:
Store the current window.location.href in a variable on loading of page. Why?
Otherwise each manipulation of it would result in sending a new GET
request which is probably undesirable
Manipulate the string stored
in the mentioned variable depending on the button clicked.
Preventing the submit button from firing directly but using the
manipulated string
Here the script part with some comments:
<script>
var current_get = window.location.href // Store current url
var handleget = function(part_chooser, id){
// part_chooser = country or town, id = pk to remove
var parts = current_get.split('?');
if (parts.length < 2){ // No get params to manipulate
return;
}
var get_part = parts[1];
var get_parts =get_part.split('&');
var i; // To keep order of new href if important
var str_to_handle; // Part to manipulate
for (i = 0; i < get_parts.length; i++){
if (get_parts[i].startsWith(part_chooser)){
str_to_handle = get_parts[i];
break;
}
};
// Do manipulation and store new url
var get_arr = str_to_handle.split('=');
if (get_arr.length > 0){
params = get_arr[1].split('%2C+');
var index = params.indexOf(id.toString());
if (index >= 0){
params.splice(index, 1);
params = params.join('%2C+');
get_parts[i] = part_chooser + '=' + params;
current_get = parts[0]+'?'+get_parts.join('&');
console.log(current_get);
}
};
};
$('button[type=submit]').click(function(event){
event.preventDefault();
window.location.href = current_get;
})
$('.country-btn').click(function(){
var id = $(this).data('remove');
handleget('country', id);
});
</script>
Some remarks
I intentionally did not use long chains of JS to shorten the code as IMHO it is easier to read step by step
The string manipulation can probably be shortend by clever use of regular expressions.
If your tokenfields contain all the possible ids as a comma seperated list on loading a javascript for deleting the specific id from the tokenfield could look like this:
<script>
$('.country-btn').click(function(){
var id = $(this).data('remove');
var countries = $('#tokenfield1').val().split(',');
var index = countries.indexOf(id.toString());
if (index >= 0){
countries.splice(index, 1)
}
$('#tokenfield1').val(countries.join());
})
</script>
In order to fill your tokenfield with the ids returned in the context you could do something like
<input type="text" class="form-control" id="tokenfield1" name="country"
value="{% for c in filteredcountries %}{{c.id}}{% if not forloop.last %},{% endif %}{% endfor %}">
Ok so I need some help. I am trying to have the address field from my CreateView form auto populate with the Places API result. So I see 2 options:
Override the address field in the CreateView. Use the address input for Places API as the input into the address field for my model.
Autocomplete the address field in the form, from the output of the address input (Places API lookup).
Any suggestions would be greatly appreciated. I have tried multiple different options and cant seem to get it to actually work correctly.
Thanks.
autocomple.html
var placeSearch, autocomplete;
var componentForm = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search predictions to
// geographical location types.
autocomplete = new google.maps.places.Autocomplete(
document.getElementById('autocomplete'), {types: ['geocode']});
// Avoid paying for data that you don't need by restricting the set of
// place fields that are returned to just the address components.
autocomplete.setFields(['address_component']);
// When the user selects an address from the drop-down, populate the
// address fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
console.log(place)
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details,
// and then fill-in the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle(
{center: geolocation, radius: position.coords.accuracy});
autocomplete.setBounds(circle.getBounds());
});
}
}
views.py
class PropertyCreateView(CreateView):
model = Property
fields = ['address', 'postal_code', 'price',
'details', 'sales_status', 'property_type']
def form_valid(self, form):
return super().form_valid(form)
detail.html
{% load crispy_forms_tags %}
{% block content%}
<div class="container">
<div class="card o-hidden border-0 shadow-lg my-2">
<div class="card-body p-0">
<div class="row">
<div class="col-lg-12">
<div class="p-5">
<div class="text-center">
<h1 class="h4 text-gray-900 mb-4">New Property</h1>
</div>
<form method="POST">
{% csrf_token %}
<div class="form-group">
<label>Address: </label>
<div id="locationField">
<input id="autocomplete"
placeholder="Enter your address"
onFocus="geolocate()"
type="text"
name="full_address"
class="form-control"
/>
</div>
</div>
<fieldset class="form-group">
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-primary btn-user btn-block"" type="submit">Add Property</button>
</div>
</form>
<hr>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'properties_app/autocomplete.html' %}
I have a template structure similar to this:
#X_List.html
<div class="row">
{% include './X_List_Table.html' %}
</div>
<div id="confirm" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="testmodal" aria-hidden="true">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content"></div>
</div>
</div>
#X_List_Table.html
<table>
<thead>
<tr>
<th>Desc</th>
<th>Activate</th>
</tr>
</thead>
<tbody>
{% for item in x_list %}
<tr>
<td>{{ item.id }}</td>
<td><a data-toggle="modal" href="{% url 'x:x_quantity' item.id %}" data-target="#confirm">Click me</a></td>
</tr>
{% endfor %}
</tbody>
</table>
My view is defined as:
#views.py
def x_quantity(request, id):
return render(request, 'modal.html', {'quantity': X.objects.filter(pk=id).count()}
and the modal.html:
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h3>Attention</h3>
</div>
<div class="modal-body">
<p>The number of elements is {{ quantity }}</p>
</div>
<div class="modal-footer"></div>
The problem is:
Supposing that I have 2 elements in the table, their urls would be:
'x/x_quantity/1'
'x/x_quantity/2'
Consider that for these elements:
One returns a QuerySet with atleast 1 element
One returns an empty QuerySet
When I click on the link, it should run the view, get the quantity based on the id of the element, return it as a context variable to the modal so it can be displayed.
The problem is:
When I click on a link, the view is being called with the id of the element, which can be confirmed by looking at the server shell [06/Apr/2018 17:00:23] "GET /x/x_quantity/1 HTTP/1.1" 200 898.
If I click on the other element, THE VIEW IS NOT BEING CALLED, there is no request going out.
What I intend to do is to display a modal with the quantity of the element clicked.
Is this a confusion on my part regarding how the {% url 'app:app_view' var %} should behave on a href or I'm not supposed to do this and should, instead, use AJAX?
Perhaps this is related with "refreshing" context variables as well?
The explanation for the behavior you are seeing can be found in the Bootstap documentation:
If a remote URL is provided, content will be loaded one time via
jQuery's load method and injected into the .modal-content div. If
you're using the data-api, you may alternatively use the href
attribute to specify the remote source. An example of this is shown
below:
If you want to use the same modal to load different content, you have to use Ajax.
A (quite ugly) workaround would be to render a modal for each item in x_list. Just be aware that the value doesn't get updated if you open the same modal twice.
Let me first clarify that before this example I have never used Bootstrap. I found your question interesting so I played a little bit with Bootstrap CDN. Also I do not use a lot of Javascript so everyone feel free to correct any bad practices.
I think what you want is doable using AJAX so here is my solution:
I changed a link to a button because all the modal examples had buttons not links :)
#X_List.html
<table>
<thead>
<tr>
<th>Desc</th>
<th>Activate</th>
</tr>
</thead>
<tbody>
{% for x in x_list %}
<tr>
<td>{{ x.id }}</td>
<td><button id="{{ x.id }}" type="button" class="btn btn-info modal-btn">Click me</button></td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal">× </button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Javascript (you will need js-cookie - I used CDN) passes the id to the server side and then shows received quantity at the end in a modal.
$(document).ready(function() {
$(".modal-btn").click(function(event) {
var x_id = $(event.target).attr('id');
$.ajax({
url : "/ajaxquantity/",
type : "POST",
dataType: "json",
data : {
x_id : x_id,
csrfmiddlewaretoken: Cookies.get('csrftoken')
},
success : function(json) {
var quantity = json.quantity;
$(".modal-title").text('Id: ' + x_id);
$(".modal-body").text('Quantity: ' + quantity);
$('#myModal').modal('show');
},
error : function(xhr,errmsg,err) {
alert(xhr.status + ": " + xhr.responseText);
}
});
return false;
});
});
Then you need to add this line into urlpatterns list:
url(r'^ajaxquantity/$', views.ajaxquantity, name='ajaxquantity')
And finally the server side. I do not know how your model looks like so here I used your query from the question.
# views.py
def ajaxquantity(request):
if "x_id" in request.POST:
response_dict = {}
x_id = request.POST.get('x_id')
quantity = X.objects.filter(pk=id).count()
response_dict.update({'quantity': quantity})
return JsonResponse(response_dict)
else:
return render(request, 'yourapp/X_List.html')
So this worked for me (with a little different QuerySet). It is very important that Jquery is only defined once!!!
Keep in mind that this is a minimal working example.
I've created some templates in Django, and I want to translate them to work in ReactJS. The thing that I'm struggling with the most is the regroup function. There's a number of ways I've thought of approaching it, but I think the easiest is probably to do it within the component. All I've managed to do is map the items which generates the entire list, but I need to have the items dynamically grouped before iterating each group.
In React I'd like to be able to apply a command like items.groupBy('start_time').groupBy('event_name').map(item =>
The output should be 'start_time', 'event_name', and then the rest of the data within each event group. Each 'start_time' will contain multiple events. I'd like to keep the code as concise as possible.
This is the Django template:
{% if event_list %}
<div id="accordian" class="panel-group" role="tablist" aria-multiselectable="true">
{% regroup event_list by start_time as start_time_list %}
{% for start_time in start_time_list %}
<div class="row start-time">
<div class="col">
<h6 class="text-muted">{{ start_time.grouper|date:' d-m-Y H:i' }}</h6>
</div>
</div>
{% regroup start_time.list by event as events_list_by_start_time %}
{% for event_name in events_list_by_start_time %}
<div class="panel panel-default">
<div class="card-header" id="{{ event_name.grouper|slugify }}">
<div class="panel-heading">
<h5 class="panel-title">
<a data-toggle="collapse" data-parent="#accordian" href="#collapse-{{ event_name.grouper|slugify }}">
{{ event_name.grouper|title }}
</a>
</h5>
</div>
</div>
<div id="collapse-{{ event_name.grouper|slugify }}" class="panel-collapse collapse in">
<div class="panel-body">
{% for item in event_name.list %}
# continue iterating the items in the list
This is the render method from the React component:
render() {
const { error, isLoaded, items, groups } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
{items.map(item => (
<h4 key={item.id}>{item.event}</h4>
)
)}
</div>
);
}
}
}
If you are setting up a React front end, it would be more sensible not to mix django templates with the react front end components. Instead set up a Django/DRF backend api that will feed your react components with JSON data.
To translate this template from django to react, you just have to reimplement the regroup template tag as a javascript function. For pretty much any django template tag, you can easily find some javascript library that does the same. This is not included in React.js, but instead you can import utilities from libraries such as underscore or moment.js etc.
This is the sample django template code from the example in the documentation for the {% regroup %} template tag.
{% regroup cities by country as country_list %}
<ul>
{% for country in country_list %}
<li>{{ country.grouper }}
<ul>
{% for city in country.list %}
<li>{{ city.name }}: {{ city.population }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Here's how you could do it with react.js
// React ( or javascript ) doesn't come with a groupby function built in.
// But we can write our own. You'll also find this kind of stuff in lots
// of javascript toolsets, such as lowdash, Ramda.js etc.
const groupBy = (key, data) => {
const groups = {}
data.forEach(entry => {
const {[key]: groupkey, ...props} = entry
const group = groups[groupkey] = groups[groupkey] || []
group.push(props)
})
return groups
}
// I'll define a dumb component function for each nested level of the list.
// You can also write a big render function with everything included,
// but I find this much more readable – and reusable.
const City = ({ name, population }) => (
<li> {name}: {population} </li>
)
const Country = ({ country, cities }) => (
<li>
{country}
<ul>{cities.map(props => <City key={props.name} {...props} />)}</ul>
</li>
)
const CityList = ({ cities }) => {
const groups = Object.entries(groupBy("country", cities))
return (
<ul>
{groups.map(([country, cities]) => (
<Country key={country} country={country} cities={cities} />
))}
</ul>
)
}
// We'll use the exact same data from the django docs example.
const data = [
{ name: "Mumbai", population: "19,000,000", country: "India" },
{ name: "Calcutta", population: "15,000,000", country: "India" },
{ name: "New York", population: "20,000,000", country: "USA" },
{ name: "Chicago", population: "7,000,000", country: "USA" },
{ name: "Tokyo", population: "33,000,000", country: "Japan" }
]
ReactDOM.render(<CityList cities={data} />, document.getElementById("app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main id=app></main>
If you run the snippet above, you should get the exact same output as what you find in the original django example.
<ul>
<li>India
<ul>
<li>Mumbai: 19,000,000</li>
<li>Calcutta: 15,000,000</li>
</ul>
</li>
<li>USA
<ul>
<li>New York: 20,000,000</li>
<li>Chicago: 7,000,000</li>
</ul>
</li>
<li>Japan
<ul>
<li>Tokyo: 33,000,000</li>
</ul>
</li>
</ul>