Django: Best practices for using Model data in clientside javascript - javascript

I'm new to working with django and I am trying to send information to my clientside javascript. I have a list of FooBarModels that I want to use with my clientside Javascript. Currently, I'm using the django template to generate an array of dictionaries that holds the information I'll use with the Javascript.
This feels like a hacky solution, is there a better way to achieve this?
models.py
class FooBarModel(models.Model):
bar = models.ForeignKey(Bar, on_delete=models.CASCADE)
#property
def desc(self):
# logic here
#property
def display_name(self):
# logic here
template.html
<script>
var $fb_list = [
{% for fb in foobar_list %}
{ "name": {{fb.display_name}}, "desc": {{fb.display_name}} },
{% endfor %}
]
// a bunch of code that uses the $fb_list
</script>
What's the best practice for handling this type of situation? Should I be sending a package of JSON to the clientside and parsing it?

This is called Serialization, Which means converting a django django.db.Model to JSON, the inverse of this is called Deserialization which means converting the JSON to a native object, this time -if it's a valid object-.
Django supports it natively, And DRF extends this support.
Here's an example with django itself.
from django.core import serializers
data = serializers.serialize("json", SomeModel.objects.all()) # xml is supported too
print(data) # prints model as JSON
or
JSONSerializer = serializers.get_serializer("json")
json_serializer = JSONSerializer()
json_serializer.serialize(queryset)
data = json_serializer.getvalue()
print(data)
It needs a queryset EVEN if you are serializing a single object.

Related

Can I interpret Django object values as JSON data within the JS script in my HTML?

I've hit a bit of a stumbling block with a Django project having added a chart to a page using Chart.js only to find that it relies on the data inputted being in JSON format. I've researched ways of converting Django object values into JSON, such as serializing and re-writing my views, but for a number of reasons these aren't ideal options for me. Is there a way to convert Django object data to JSON data within the JS script?
I have an 'Accelerator' Django model with five separate decimal fields which ultimately need to be converted to JSON to feature in the chart. For each object created, the value of each field is prone to change (they are determined by a method calculating the mean value of separate values inputted into a separate model). I have an 'accelerator_detail' HTML template which renders and displays the various values of individual objects created using the Accelerator model.
This template is heavily reliant on Django placeholders and Python logic calling on object values, which is one reason why I'm hesitant about attempting to serialize the Django objects as JSON within my views (presumably this would mean I would have to re-write this template).
I've shared some of the relevant code below to provide a better understanding. Currently, the data key in my JS script is populated with dummy data but this is where my Django object values need to be stored:
// One of my model fields which needs converting to JSON
class Accelerator(models.Model):
avg_mentorship = models.DecimalField(decimal_places=2, max_digits=3)
#property
def avg_mentorship(self):
quantity = Review.objects.filter(subject=self)
mentorship_result = Review.objects.filter(subject=self).aggregate(avg_mentorship=Avg('mentorship'))['avg_mentorship']
return mentorship_result if len(quantity) > 0 else 0
// My accelerator_detail view
def accelerator_detail(request, pk):
accelerator = get_object_or_404(Accelerator, pk=pk)
reviews = Review.objects.all()
context = {
'accelerator': accelerator,
'reviews': reviews,
}
return render(request, 'reviews/accelerator_detail.html', context)
// JS script within my HTML template
<script>
var myChart = document.getElementById('accRatings').getContext('2d');
var ratingsChart = new Chart(myChart, {
type:'horizontalBar',
data:{
labels:['Mentorship', 'Hiring', 'Community', 'Fundraising', 'Corporate Development'],
datasets:[{
data:[
4.1,
4.4,
3.9,
3.6,
4.2
],
}]
},
});
</script>
Sorry I can't give this in a comment yet, have you looked into this yet? Django Generic JSON views. Seems to me like it would fit nicely into your project and help with the output you need.
Your data is already output by the view. You can just convert to JSON as you serve it from within the view.
Great example provided by SimpleIsBetterThenComplex:
from django.http import JsonResponse
def profile(request):
data = {
'name': 'Vitor',
'location': 'Finland',
'is_active': True,
'count': 28
}
return JsonResponse(data)

FOR loop over a Django model in a JS script

I just started a web app using Django and HTML/Javascript templates.
My Django spot app contains a Spot model that is sent to a HTML template - to be used with the Google Map Api. I've encountered a problem when looping over the variable spots containing Spot.objects.all().
It seems the problem comes from the way I send the data to the HMTL file.
----------------------------------------- Spot Django-app : models.py --------------------------------------------
class Spot(models.Model):
idn = models.IntegerField(unique = True)
name = models.CharField(max_length = 200)
longitude = models.FloatField()
latitude = models.FloatField()
------------------------------------------------- HTML / JS -----------------------------------------------
<script type="text/javascript">
var IDs = []
var names = []
var lat = []
var lng = []
{ % for spot in spots % }
IDS.push( {{spot.idn}} );
names.push( {{spot.name}} );
lat.push( {{spot.latitude}} );
lng.push( {{spot.longitude}} );
{ % endfor % }
Then, the lists do not contain any data that can be used afterwards. Worse, the HTML file does not work if the names.push( {{spot.name}} ) is un-commented.
----------------------------------------- Spot Django-app : views.py --------------------------------------------
from spots.models import Spot
def index(request):
return render(request, 'index.html', {'spots':Spot.objects.all()})
Thanks to the other stackoverflow questions (listed below), I also tried to serialize the Spot.objects.all() either with django.core.serializers.serialize("json", Spot.objects.all() ) or by creating my own serializer (thanks to Django_REST). The problem remains the same. So is the problem in the way I parse my data with JS?
I've look the following link :
Returning JSON array from a Django view to a template
django for loop in a .html template page (newbie)
Django FOR LOOP in JavaScript
with no success. So if the answer is included or related to these topics, would you mind explaining me something I've been working around for days ...
EDIT:
The problem was plural:
Serializing the data (or not ; I did not for now but everyone who answered agreed to say that it's better to)
Adding the quotes from {{ spot.name }} to '{{ spot.name }}', only to non Integer/Float models (i.e. only the models.CharFields fields)
Google Maps Api may return errors for some (longitude, latitude) tuples even if they are well-defined
Django will not recognize those template tags because you have spaces between the brace and the percent. So, there is no looping being done at all. You need to write them in the correct format:
{% for spot in spots %}
...
{% endfor %}
Once you do that, you'll start getting all sorts of JS syntax errors because you have not wrapped any of your data in quotes. But, as the comments say, doing this as JSON would be much better.
Even that I think that serializing your data into Json is much better idea. Your javascript code does not work because e.g. {{ spot.name }} will render raw string so for javascript to understand it you need to put it in quotes (and of course semicolon after each line).
names.push('{{spot.name}}');

How to pass data from python to javascript in web2py

I see some relevant posts to my query.
Tornado is used in the below link
How to pass variable from python to javascript
I know that it can be done using json but I am not clear how to implement it.
In the web2py default controller I am returning a dictionary which contains the latitudes and longitudes.
def index():
lat_long_list=[]
info1 = {'lat':'1.0032','long':'2.00003','name':'Akash'}
info2 = {'lat':'1.2312','long':'-1.0034','name':'Kalyan'}
lat_long_list.append(info1)
lat_long_list.append(info2)
return dict(lat_long_list=lat_long_list)
In java script I want to iterate through the list of dictionaries and mark the points on the google maps.
I cannot say
<script>
{{ for lat_long_rec in lat_long_list :}}
var name = {{=lat_long_rec['name']}}
{{ pass }}
</script>
This fails. An alternative to handle this is to write the list into an xml and from javascript read the file but I dont want to achieve it this way as writing to file is non performant. Let me know how best this can achieved.
Convert the Python list to JSON and pass that to the view to insert in the Javascript code:
from gluon.serializers import json
return dict(lat_long_list=json(lat_long_list))
In the view:
<script>
...
var latLongList = {{=XML(lat_long_list)}}
...
</script>

Passing Google App Engine datastore models to javascript code

I know that the Jinja2 library allows me to pass datastore models from my python code to html and access this data from inside the html code as shown in this example . However Jinja2 isn't compatible with javascript and I want to access the data inside my Javascript code . What is the simplest templating library which allows to iterate over my datastore entities in Javascript ? I've heard about things like Mustache and Jquery , I think they look a bit too complicated. Is there anything simpler?
You should create a python controller which serves JSON formatted data, which any Javascript library (especially jQuery) can consume from. Then, setup the Jinja2 template to contain some Javascript which calls, loads and displays said data.
One more approach to consider: If the Python object is not dynamic, you may want to use json.dumps() to store it as a TextProperty, and simply JSON.parse(unescape(model_text)) on the JS side. Reduces the overhead, and memory hit which can be important when trying to stay within an F1 limit. For example, I run an instance that very easily runs inside an F1. There is one large dictionary object that we deliver. Were this object to exist as a Python dictionary inside the instance we would kill the instance due to the soft memory limit. Using the TextProperty approach we can pass this large dict to the client without any issues. (Note: we did have to momentarily boost our instance up to an F4 when initially creating this object -- something incredibly easy inside the Admin web page.) With more dynamic objects, answers above apply.
Jinja2 and Javascript play fine together. You need to arrange to have template expansion emit your Python data structures into a JS-friendly form.
https://sites.google.com/a/khanacademy.org/forge/technical/autoescape-in-jinja2-templates covers it fairly well. (Note the use of the escapejs filter.)
It works. I had to serialize(convert) my datastore entities to json format, which Javascript understands well. I created a function which converts every instance of my datastore into a dictionnary then encapsulates all these instances into a list which is then converted to Json using json.dumps. When I pass this result to the Java script , I can then easily access my values as seen below.
import json
import webapp2
from google.appengine.ext import db
import jinja2
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
# serialize datastore model to JSON format
def serialize(model):
allInstances = model.all() # fetching every instance of model
itemsList = [] #initial empty list
for p in allInstances:
d = db.to_dict(p)
itemsList.append(d)
return json.dumps(itemsList)
class myModel(db.Model):
v = db.FloatProperty()
c = db.FloatProperty()
tdate = db.DateTimeProperty(auto_now_add=True)
class MainPage(webapp2.RequestHandler):
def get(self):
myModel(v=4.5, c=3.0).put()
#creating template variables
template_values = {
'json_data': serialize(myModel)
}
template = JINJA_ENVIRONMENT.get_template('index.html')
self.response.write(template.render(template_values))
Inside my 'index.html' file, I have:
{% autoescape true %}
<!DOCTYPE html>
<html>
<head>
<title> webpage </title>
<script type="text/javascript">
// I retrieve my data here
var results = "{{ json_data }}";
for(var i = 0; i < db_results.length; i++) {
document.write("myModel instance:" + i + results[i] + "<br>");
}
</script>
</head>
<body>
</body>
</html>
{% endautoescape %}
It has nothing to do with compatibility. Jinja is server side templating. You can use javascript for client side coding.
Using Jinja you can create HTML, which can be accessed by javascript like normal HTML.
To send datastore entities to your client you can use Jinja to pass a Python list or use a json webservice.

django dynamic filtered form

I am converting a project from php to Django and have run into the issue of filtered menus. I have a form:
class SearchForm(forms.Form):
genus = forms.CharField(max_length=100)
# species
species = forms.CharField(max_length=100)
# island group
island_group = forms.ModelChoiceField(queryset=Localitymayor.objects.values_list('islandgroup', flat=True).distinct('islandgroup').exclude(islandgroup="n/a").order_by('islandgroup'), empty_label=_("Not Specified"))
# island name
island_name = forms.ModelChoiceField(queryset=Localitymayor.objects.values_list('islandname', flat=True).distinct('islandname').exclude(islandname="n/a").order_by('islandname'), empty_label=_("Not Specified"))
my template is along the lines of:
<form action="{% url cdrs_search %}" method="post">{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
</form>
Now I want to filter the output of the island_name depending on the selection of island_group. In my php project I managed this with an ajax onChange call to another php script. However, I am a little lost how to do this in Django. As this is my first time working with ajax in Django I would appreciate any suggestions as to the best practice way of dealing with this simple but common filtered menu problem. Thanks in advance.
you need to have a view for your ajax script to hit to ask for a list of names based on a group, e.g.
# views.py
def ajax_endpoint(request):
# leaving error checking to you
group = request.GET.get('group')
names_list = avalable_names(group) # some function or db call
return HttpResponse(json.dumps(names_list))
One option is to do this in javascript. You can make a call to a separate view using an ajax request from jQuery. The job of this separate view is to handle the server side sorting of your model data (island)name based on the user selected island_group). Then you can use javascript to repopulate your forms using the response from the view. Some good examples on how to do this can be found in this blog (it is a little dense, but is very useful), in an article on how to do this with javascript, and in this tutorial (highly recommended).
There is also a good SO post that explains why it is necessary to do it this way, it may be a bit pedantic to include but it helped clarify things for me while creating filtered forms. Just look at the accepted answer for that question.
Here a modification of code from the ticket form of betspire.com. This code snippet depends on having jQuery loaded.
Here's the javascript you need:
function update_select(select, data) {
select.find('option').remove();
select.append($('<option value="">-------</option>'));
for (var i in data) {
select.append($('<option value="'+data[i][0]+'">'+data[i][1]+'</option>'));
}
}
$('select[name=island_group]').live('change', function(e) {
$.get(
'{% url island_name_choices_for_island_group %}',
{
'island_group': $(this).val(),
},
function(data, textStatus, jqXHR) {
update_select($('select[name=island_name]'), data);
},
'json'
);
});
Add to urls:
url(
r'island_name/choices/$',
'island_name_choices_for_island_group', {
}, 'island_name_choices_for_island_group',
),
Add to views:
from django.utils import simplejson
from models import *
def island_name_choices_for_island_group(request, qs=None):
if qs is None:
# Change the default QS to your needs
# (you didn't specify it)
qs = Island.objects.all()
if request.GET.get('island_group'):
# assuming your Island model has an FK named island_group to model IslandGroup
qs = qs.filter(island_group__pk=request.GET.get('island_group'))
results = []
for choice in qs:
results.append((choice.pk, choice.name))
return http.HttpResponse(simplejson.dumps(results))
Please let me know if you run in any troubble.

Categories