How do I JSON parse a Django queryset? - python

So I'm trying to parse each object in my Django queryset, and deal with the data through JavaScript. Below is my code (simplified) :
views.py (using Django Paginator, but the basic idea is the same.)
def main_page(request):
all_contents = Contents.objects.all()
paginator_contents = Paginator(contents,10)
page = request.GET.get('page')
all_contents_paginated = paginator_contents.get_page(page)
context = {
'contents' : contents,
'all_contents_paginated' : all_contents_paginated
}
return render(request, 'main/home.html', context)
template
{% for c in all_contents_paginated %}
<div class="text-m">
{{c.author}}
</div>
<div class="text-s" onclick="DetailModal('{{c}}')">
{{c.body}}
</div>
{% endfor %}
<script>
function DetailModal(c) {
}
</script>
Now obviously, the '{{c}}' cannot be parsed into JSON since it's string. I want to parse it into JSON in function DetailModal() and display the data in a separate modal element or do any other stuff with each data. But I can't figure out how to parse each object in the Django queryset.
Any ideas? Thanks.

you just modify your script to parse your item:
<script>
function DetailModal(c) {
const obj = JSON.parse(c);
console.log(obj.author);
console.log(obj.body);
}
</script>

Related

Django passing context in JsonResponse

I am developing a webpage with filters to filter the results on the page.
A Ajax is called, which sends the filters to my Django back-end. The results are filtered and the data should be passed back to the front-end.
So now I need to pass my results of the models with context to the front-end. This leads to some problems.
My Ajax:
$(document).on('change', '#test-form', function (e) {
e.preventDefault()
var tags = [];
$('input[name="tags[]"]:checked').each(function(i){
return tags[i] = $(this).val();
});
$.ajax({
type: 'POST',
cache: false,
url: "{% url 'core:jobSearch_nosearch' %}",
data: {
tags: tags,
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
},
success: function(data) {
console.log('yey')
console.log(data)
}
});
});
Here my View:
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.functional import Promise
class LazyEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return str(obj)
return super().default(obj)
def jobSearch(request, **search):
companies = Company.objects.all()
if request.method == 'POST':
ads = Ad.objects.all()
search_job = request.GET.get('search')
if search_job:
ads = Ad.objects.filter(title__contains=search_job)
tag_filter = request.POST.getlist('tags[]')
for tag in tag_filter:
print(tag)
ads = ads.filter(tag__name=tag)
print(ads)
context = {'companies': companies, 'ads': ads}
# context = {'companies': list(companies)}
# context = {'msg': 'Success'}
# return JsonResponse(serialize('json', ads, cls=LazyEncoder), safe=False)
return JsonResponse(context)
else:
ads = Ad.objects.all()
context = {'companies': companies, 'ads': ads}
return render(request, 'core/jobSearch.html', context)
As you can see I tried different things in the my view. This return JsonResponse(serialize('json', ads, cls=LazyEncoder), safe=False) passes the result of one model. But I have two models which I have to pass to the front-end.
Additionally, I would like to get the data and being able to use it with the html template language.
In this way: (example)
{% for a in ads %}
{% a %}
{% endfor %}
Is that even possible with Django and Ajax, or is there another way to filter results and passing them without reloading the page?
django template tag work on rendering html content and you can not pass argument after render page so after loading page you can not use
{% for a in ads %}
{% a %}
{% endfor %}
if you have not pass that arguments
you can use api and js for this work
i suggest you read about drf
you can do this work with api and js

Django CreateView not updating context data

I have a form in a bootstrap modal, and I want to keep that modal open after submission. I am using CreateView and trying to pass an additional variable to the template in front-end where I could check if the flag is set or not, but the flag is always False even after submission. Here is what I have:
url.py
from django.urls import path
from .views import MescData
urlpatterns = [
path('mesc', MescData.as_view(), name='mesc')
]
views.py
from django.urls import reverse
from django.views.generic.edit import CreateView
from .forms import MescForm
from .models import Mesc
class MescData(CreateView):
model = Mesc
form_class = MescForm
template_name = 'Materials/mesc_data.html'
successful_submit = False # Flag to keep the add entry modal open
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['successful_submit'] = self.successful_submit
return context
def get_success_url(self):
return reverse('mesc')
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
self.successful_submit = True
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form, **kwargs):
# self.successful_submit = True
return super(MescData, self).form_valid(form, **kwargs)
And in Template, I'm checking it like this:
{% if successful_submit %}
<h1>Flag is set</h1>
{% endif %}
Is there a way to pass non-form-related data in CreateView to the template without having to change the url.py (i.e. adding variable data to url path)?
EDIT:
I tried printing the self.successful_submit in form_valid() and post() methods and it is indeed being updated to True, but in the template it is still being passed on as False.
This is the core problem: "I have a form in a bootstrap modal, and I want to keep that modal open after submission."
Simple answer: Use Ajax.
We do now have HTML over the wire as a paradigm gaining popularity, but I'm not familiar enough with it to discuss it. So I will use Ajax to show a solution. This particular solution uses a general ajax template, and the result of a post is a rendered Django template that you can use to just replace the HTML in your already rendered page.
Also, few people like JavaScript, but that is not a good enough reason to avoid using it. It is basically mandatory in any web application you're running. Even HTML over the wire uses a minimal amount of JavaScript to accomplish its goals.
First, write your Ajax view. I'm using django-rest-framework classes for this, and I'm providing an example for filling out a Calibration record. You can use whatever form you want; this is just my recipe for handling modals that you want keep open. In this view, I return a JSON response if the POST was successful. Otherwise, I return a rendered Django template.
from rest_framework.generics import (CreateAPIView, RetrieveAPIView,
UpdateAPIView, get_object_or_404)
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response
class CalibrationCreateAjaxView(CreateAPIView, UpdateAPIView, RetrieveAPIView):
renderer_classes = (TemplateHTMLRenderer,)
template_name = "documents/form/cal.html"
def post(self, request, *args, **kwargs):
context = self.get_context(request)
calibration_form = context['calibration_form']
if calibration_form.is_valid():
calibration_form.save()
request.accepted_renderer = JSONRenderer()
return Response(status=201)
return Response(context, status=400)
def get(self, request, *args, **kwargs):
return Response(self.get_context(request))
#staticmethod
def get_context(request):
pk = request.GET.get("pk")
calibration_entry = get_object_or_404(CalibrationEntry, pk=pk) if pk else None
return {
'calibration_form': CalibrationFormAjax(request.POST or None, instance=calibration_entry)
}
I have my view template as well. It takes advantage of request.is_ajax, which is being deprecated. You'll need to add some middleware to keep using it. Here's my middleware. Add it to your settings file as well.
class IsAjaxMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
"""
request.is_ajax is being removed in Django 4
Since we depend on this in our templates, we are adding this attribute to request
Please review:
https://docs.djangoproject.com/en/4.0/releases/3.1/#id2
"""
def __call__(self, request):
request.is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest'
return self.get_response(request)
general/ajax_modal.html
<!-- {% block modal_id %}{% endblock %}{% block modal_title %}{% endblock %} -->
{% block modal_body %}
{% endblock modal_body %}
general/modal.html
<div class="modal fade" id="{% block modal_id %}{{ modal_id }}{% endblock modal_id %}" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title">
{% block modal_title %}{{ modal_title }}{% endblock modal_title %}
</h4>
</div>
<div class="modal-body">
{% block modal_body %}
{% endblock modal_body %}
</div>
</div>
</div>
</div>
Even though, we're using Crispy Forms, you can get away without using it. I also have a general templatetag library that renders any errors on a form. You can write your own.
documents/form/cal.html
{% extends request.is_ajax|yesno:'general\ajax_modal.html,general\modal.html' %}
{% load crispy_forms_tags general %}
{% block modal_id %}new-cal-modal{% endblock modal_id %}
{% block modal_title %}Enter Calibration Information{% endblock modal_title %}
{% block modal_body %}
<div id="new-cal-form-container">
<form action="{% url 'calibration-create' %}" method="post" id="new-cal-modal-form" autocomplete="off" novalidate>
{% if request.is_ajax %}
{% crispy calibration_form %}
{% form_errors calibration_form %}
{% endif %}
<button type="submit" name="Submit" value="Save" class="btn btn-success button" id="submit">save</button>
</form>
</div>
{% endblock modal_body %}
So now that the Ajax view is all set up, I go back to the main page that will render the modal dialog when the user clicks a button. I have a block called "extraContent" in which I include the template of the modal form.
{% block extraContent %}
{% include 'documents/form/cal.html' %}
{% endblock extraContent %}
And now, the JavaScript, which requires jQuery, that I've added to the template. I guess I made my own jQuery plugin on top of that...
$.fn.modalFormContainer = function(optionsObject) {
//call it on the modal div (the div that contains a modal-dialog, which contains modal-header, modal-body, etc
// we expect there to be a form within that div, and that form should have an action url
// when buttons that trigger the modal are clicked, the form is fetched from that action url and replaced.
// optionsObject has formAfterLoadFunction and ajaxDoneFunction
var options = $.extend({}, $.fn.modalFormContainer.defaults, optionsObject);
var $modalFormContainer = $(this);
// get the buttons that trigger this modal to open
// add a click event so that the form is fetched whenever the buttons are clicked
// if data-pk is an attribute on the button, apply that to the querystring of the
// ajaxURL when fetching the form
var modalID = $modalFormContainer.prop("id");
var modalFormButtonSelector = "[data-target=#" + modalID + "][data-toggle=modal]";
function handleModalButtonClick(event) {
//does the button have an associated pk? if so add the pk to the querystring of the ajax url
// this is wrapped in a form so that it gets replaced by the ajax response.
var $button = $(this);
if (!$button.hasClass("disabled") && !$button.prop("disabled")) { //only do it if the button is "enabled"
var $placeholder = $("<form><h1>loading...</h1></form>");
var $modalForm = $modalFormContainer.find("form");
var ajaxURL = $modalForm.prop("action");
$modalForm.replaceWith($placeholder);
var pk = $button.data().pk;
if (pk) {
if (ajaxURL.indexOf("?") > 0) {
ajaxURL += "&pk=" + pk;
} else {
ajaxURL += "?pk=" + pk;
}
}
//fetch the form and replace $modalFormContainer's contents with it
$.ajax({
type: "GET",
url: ajaxURL
}).done(function(response) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(response);
$modalForm = $modalFormContainer.find("form"); //we would still need to find the form
options.formAfterLoadFunction($modalForm);
});
} else {
return false; //don't trigger the modal.
}
}
//using delegation here so that dynamically added buttons will still have the behavior.
// maybe use something more specific than '.main-panel' to help with performance?
$(".main-panel").on("click", modalFormButtonSelector, handleModalButtonClick);
$modalFormContainer.on("submit", "form", function(event) {
// Stop the browser from submitting the form
event.preventDefault();
var $modalForm = $(event.target);
var ajaxURL = $modalForm.prop("action");
$modalForm.find("[type=submit]").addClass("disabled").prop("disabled", true);
var formData = $modalForm.serialize();
var internal_options = {
url: ajaxURL,
type: "POST",
data: formData
};
// file upload forms have and enctype attribute
// we should not process files to be converted into strings
if ($modalForm.attr("enctype") === "multipart/form-data") {
internal_options.processData = false;
internal_options.contentType = false;
internal_options.cache = false;
formData = new FormData($modalForm.get(0));
internal_options.data = formData;
}
$.ajax(internal_options).done(function(response) {
// blank out the form
$modalForm.find("input:visible, select:visible, textarea:visible").val("");
// remove errors on the form
$modalForm.find(".has-error").removeClass("has-error");
$modalForm.find("[id^=error]").remove();
$modalForm.find(".alert.alert-block.alert-danger").remove();
// hide the modal
$(".modal-header .close").click();
options.ajaxDoneFunction(response);
}).fail(function(data) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(data.responseText);
options.formAfterLoadFunction($modalForm);
});
});
return this;
};
$.fn.modalFormContainer.defaults = {
formAfterLoadFunction: function($form) { return; },
ajaxDoneFunction: function(response) { return; }
};
$("#new-cal-modal").modalFormContainer({
formAfterLoadFunction: function($modalForm) {
$(".datetimeinput").datepicker('destroy');
$(".datetimeinput").datepicker();
},
ajaxDoneFunction: function(event) {
location.reload();
}
});
So upon reviewing this, I've realized that this recipe is far more complicated than I have tricked myself into believing. I sincerely apologize for that. I hope that you can review the code and get an idea of what is happening. There are some edge cases, such as dealing with dates and file uploads, that this recipe handles right now, but you may not actually need them. I should mention that the application that this came from is using Bootstrap 3, so its styling is not updated to the current Bootstrap 5 as of this writing. I should add that the main content of the app has a class of "main-panel" as used in this not-so-generic jQuery plugin.
I'm worried that I've gone and overwhelmed you into maintaining your position of trying to keep using a standard POST request. I guess you could just re-render the template with your POST since it'll be standard practice in your project. You could still get away without using a query string that way.

How to use json.dumps() to render python object in Flask app?

Hi all: I'm using Flask to develop a simple web app. I have a python function that return a collection of objects that I then want to render inside a template (i.e {{ object1.value }}) in a html page. I was thinking about creating a dictionary containing the object values that would then be passed on to the page as a jsonify string through a GET request.
The Flask app looks like this:
#app.route('/')
def hello():
python_func(object1,object2,object3...)
data = json.dumps({object1.key: object1.value, object2.key: object2.value ...})
if request.is_xhr:
return jsonify(data=data)
if request.method == "GET":
return render_template('main.html',data=data)
if __name__ == '__main__':
app.run()
The html page looks like this:
<div class ="dice">
<div class ="dice-1">
<div class="card" id ="card1" >
<p>{{ data }}</p>
</div>
</div>
And script with an event button. When button is clicked the object values are updated:
<script>
$(function() {
$('#roll-button').click(function() {
$.get('/', function(data){
document.getElementById("card1").innerHTML = data.data;
})
});
});
</script>
This seems to work to pass the object values to the template. I can see a string object with all the object keys and values when I update the page.
My problem is that I don't know how to use the string inside the template. I am confused about the documentation (https://docs.python.org/3.4/library/json.html) on the subject about decoding json.
Hence this question: how can parse the string containing all the object values to render inside the template to ideally look like this: {{ object1.value }}, {{ object2.value }}, {{ object3.other_attribute }} etc...
Can I create a python dictionary from the json string for me to use in the template?
Thank you for your help!
Cam
Don't pass JSON to your template, pass a dictionary. If you want non-dynamic rendering of data in HTML go with Jinja and flask's built in stuff.
from flask import render_template
#api.route('/foo', methods=['GET'])
def foo():
my_dictionary = {'a':'b', 'foo':'baz'}
return render_template('foo.html', my_data=my_dictionary)
Then in your html template
<!DOCTYPE html>
<html lang="en-ca">
<body>
{% for key in my_data %}
<h1>{{key}}</h1>
<h1>{{my_data.key}}</h1>
{% endfor %}
</body>
</html>
If you want to make the data available as a javascript object on the client side for live updating you will have to make an ajax call and do some templating on the front-end. If you don't mind page reloads just stick with flask's templating engine.

How do I send an array of objects from a django class based view to my index.html file?

The Goal:
I want to use FullCalendar(https://fullcalendar.io/) to display event objects from my database. FullCalendar accepts an array of event objects as a property.
The Problem I'm having:
I can send context data back to the template with the event objects but as far as I know I can only interact with the data using Django's template tagging system. *EDIT: When I replace the hardcoded array with ptoHistory I receive the following error in the chrome console:
jquery-3.1.1.min.js:2 Uncaught ReferenceError: ptoHistory is not
defined
at HTMLDocument. ((index):141)
at j (jquery-3.1.1.min.js:2)
at k (jquery-3.1.1.min.js:2)
index.html:
{% extends 'base.html' %}
{% block content %}
//ACCESSING THE CONTEXT DATA LIKE THIS WORKS BUT I CAN'T USE ptoHistory IN MY FULLCALLENDAR SCRIPT
{% for history in ptoHistory %}
<li>{{obj.leave_type}}</li>
{% endfor %}
<div class="container">
<div id="calendar">
<!-- Calendar is injected here -->
</div>
<!----------------------- SCRIPTS ----------------------------->
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
defaultView:'month',
editable: true,
// MY ARRAY OF OBJECTS WILL REPLACE THIS HARDCODED ARRAY
events: [
{
title: 'All Day Event',
start: '2017-01-12',
},
{
title: 'Meeting',
start: '2017-01-13T10:30:26',
end: '2014-06-13T12:30:00'
},
],
});
});
</script>
{% endblock content%}
IndexView.py:
class IndexView(FormView):
template_name = 'accounts/index.html'
form_class = PtoRequestForm
success_url = 'login/'
def form_valid(self, form):
form.save()
return super(IndexView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['ptoHistory'] = PtoHistory.objects.all()
print(context['ptoHistory'])
return context
Could someone point me in the right direction?
You are right, it won't work. Django template system is able to process python objects because they are executed already before the template is finished rendering.
You could still assign python list in javascript, though, but not with python objects but json string. The solution is basically compose for only what you need in the views using values() then . I don't know what fields do you need for PtoHistory, but you could do:
# views.py
import json
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
ptoHistory = json.dumps(list(PtoHistory.objects.values()))
# or do this if you only need several fields' value
# ptoHistory = json.dumps(list(PtoHistory.objects.values('field1', 'field2')))
context['ptoHistory'] = ptoHistory
return context
// in javascript
$(document).ready(function() {
$('#calendar').fullCalendar({
defaultView:'month',
editable: true,
var histories = {{ ptoHistory|safe }};

How to jump to anchor with invalid Django form?

When my Django form fails to validate it automatically goes back to the page where the form failed but I'd like it to go to an html anchor on the page. Here's the code I have:
def detail(request, post_id):
p = get_object_or_404(Post, pk=post_id)
# get all the comments for the post
comments = p.comment_set.all().order_by('pub_date')
# to add a comment to the post
if request.method=='POST':
form = CommentForm(request.POST)
if form.is_valid():
c = Comment()
c.body = request.POST['body']
c.post = p
c.save()
return HttpResponseRedirect(reverse('journal.views.detail', kwargs={'post_id': post_id}) + "#comment-" + str(c.id))
else:
form = CommentForm()
return render(request, 'posts/detail.html', {'post': p, 'comments': comments, 'form': form})
The form is a comment form at the bottom of a blog post. When the form is invalid I'd like it to jump to the comment form's html anchor at the bottom of the page. Right now it goes to the top of the page and you have to scroll down to notice that there's an error.
I tried a couple things that didn't work. Please help!
I think that is fair enough:
<script>
function goToId() {
if (!window.location.hash) window.location = window.location + '#your-id';
}
{% if form.is_bound %}
$(window).on("load", goToId);
{% endif %}
</script>
One approach is to pass an extra key-value pair in the dict to contain the anchor name, and use JavaScript in the template to jump to it if needed.
Python side:
// ...
return render(request,
'post/detail.html',
{
// ...
'anchor': 'comment'
})
The template:
function goToOnLoadAnchor()
{
window.location.hash('{{ anchor }}'); // Or something with the same effect
}
{% ... %}
<body onload="goToOnLoadAnchor">
{% ... %}
You can also take advantage of custom template tags to add more flexibility.
CBV & jQuery
FormView
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form, anchor='my-form'))
template
<form ... id='my-form'> ... </form>
<script>
$(function () {
{% if anchor %}
$(document.body).animate({
'scrollTop': $('#{{ anchor }}').offset().top
}, 2000);
{% endif %}
});
</script>
Uses jQuery for scrolling (instead of jumping).
With Vanilla JavaScript:
{% if form.is_bound %}
<script>
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("your_id").scrollIntoView();
});
</script>
{% endif %}
It works well with the addEventListener("DOMContentLoaded") listener. Otherwise it can give some weird results.

Categories