Problem Overview:
I am creating a Django-based client with the intent of returning data from a web service. The goal of this project is to return data to the user from the web service based on the values selected by the user in a form. Upon the form submit, a query string is generated, sent to the web service and the page data is returned as a string. Currently, that data is displayed to the user in the browser. I want to provide the functionality that would allow the user to click a button and download the data.
Question:
How could I return the data to the user when they click a button in their browser for download? How do I make different options of the same data available (i.e. application/json, or text/csv)?
Current (not-working) Implementation:
I am attempting, and failing, to do the following:
views.py
Returning a render_to_response object of my template. To the template I pass the form, and the data in it's various forms.
def view(request):
#Do stuff, get data as string
#Get data into needed formats (see utils.py)
jsonData = jsonToJsonFile(dataString, fileName)
return render_to_response('template.html', {'someForm' : aForm,
'regularData' : stringData,
'jsonData' : jsonData...})
utils.py
Contains functions to take the data as a string and return response objects. This part I am unsure if I am doing correctly. I call these functions in the view to get jsonData (and csvData) into their proper formats from the original data string.
def jsonToJsonFile(dataString, fileName):
#Get the data as json
theData = json.dumps(dataString)
#Prepare to return a json file
response = HttpResponse(theData, mimetype = 'application/json')
response['Content-Disposition'] = 'attachment; filename=' + str(fileName) + '.json'
#return the response
return response
template.html
I am currently passing the responses into the template. This is where I am really lost, and have not yet begun to find a good solution. I expect I will need to use javascript to return the variables (jsonData and csvData) to the user when the button is clicked. I have attempted the use of the onclick action of the anchor class, and then using javascript to return the django variable of the response - but this really isn't working.
<li class = 'button'>
<a href = "#dataButtons" onclick = "javaScript:alert('test');">
TEST
</a>
</li>
<li class = 'button'>
<a href = "#dataButtons" onclick = "javaScript: var a = '{{ jsonData }}'; return a;">
JSON
</a>
</li>
I put the test part in there to, well, test whether or no the alert would work. It does. However, when I click the button for the json data, nothing happens.
Am I approaching this completely wrong? Or is there something small that I am missing?
Solution:
After looking at the problem a little further and talking to a colleague about it, it seems as though my problem lies in trying to pass the response object to the javascript. For those interested, I solved the problem with a little careful re-routing of the data.
views.py
In my views.py I added a couple lines of code in my main view that would set variables of two extra views (one for csv one for json) to the response objects holding the data. These two extra views would then be called when their respective buttons were pressed, returning the httpresponse and prompting the user for download.
#MAIN VIEW FUNCTION
def view(request):
#Do stuff, get data as string
#Get data into needed formats
jsonData = jsonToJsonFile(dataString, fileName)
#Set values to external view ****NEW PART****
returnJSON.jsonData = jsonData
#Render main template
return render_to_response('mainTemplate.html', {'someForm' : aForm,
'regularData' : dataString})
#SECONDARY VIEW TO RETURN JSON DATA TO USER ****NEW PART****
def returnJSON(request):
#Simply return the response
return returnJSON.jsonData
template.html
Then, when the button is pressed by the user, the anchor is linked via the url to the secondary django view that will present the download option to the user.
<li class = 'button'>
<a href = "{% url client.views.returnJSON %}">
JSON
</a>
</li>
urls.py
Lastly, I just pointed my url patterns to the view.
urlpatterns = patterns('',
(r'^somesite/$', views.view),
(r'^somesite/json$', views.returnJSON),
)
So far, this method has worked great for me! If anyone has any other suggestions, or a better method, I would certainly be open to hear it.
I think you need to change your javascript to make it start a file download - see this question & answer:
starting file download with JavaScript
Related
I am working on a flashcards app using flask. It is similar to Anki if you know about that. When I click on a deck, I want it to go to study_session.html where it displays the first card of the selected deck. Currently I have a home screen that lists the user's decks. Each list item is a button element with an onclick attribute that calls a javascript method that takes the selected deck's name as a parameter. The method sends a post request to the flask route "/study-session" along with the name of the deck. This allows the route's method to get the selected deck and send the appropriate flashcard to the "study_session.html" page to be displayed. What's wrong is that either the javascript method or the server-side route method, or both do not seem to be executing. When I click the deck I want to study, nothing happens. I would like someone to point out my mistake and offer the appropriate fix. If I need ajax, please show me how the javascript would look like. Thank you.
Here is the html that has the button with an onclick attribute:
{% for deck in user.decks %}
<li class="list-group-item">
<button class="btn btn-link pl-0" onclick="startStudySession({{ deck.deck_name }})">{{deck.deck_name}}</button>
Here is the javascript:
// Takes deckId and sends post req to study-session route.
// Then runs study-session view
function startStudySession(deckName) {
// Specify what route to send request to
fetch("/study-session", {
method: "POST",
body: JSON.stringify({deckName: deckName})
}).then((_res) => {
window.location.href = "/study-session"
});
}
Here is the route method on the python server:
#views.route('/study-session', methods=['GET', 'POST'])
def study_session():
"""
Renders study_session page with one flashcard displayed. Handles all requests on study_session page.
"""
request_data = json.loads(request.data)
cur_deck = current_user.get_deck(request_data['deckName'])
cur_card = cur_deck.notes[0].flashcards[0]
front = cur_card.front_text
back = cur_card.back_text
print("is this working?")
return jsonify('', render_template("study_session.html", user=current_user, front=front, back=back))
I use an html table where it's content can be changed with mouse drag and drop implemented. Technically, you can move the data from any table cell to another. The table size 50 row * 10 column with each cell given a unique identifier. I want to export it to .xlsx format with C# EPPlus library, and give back the exported file to client.
So I need the pass the whole table data upon a button press and post it to either a web api or an mvc controller, create an excel file (like the original html table data) and send it back to download with browser.
So the idea is to create an array which contains each of table cell's value ( of course there should be empty cells in that array), and post that array to controller.
The problem with that approach lies in the download, if I call the api or mvc controller with regular jquery's ajax.post it did not recognize the response as a file.
C# code after ajax post:
[HttpPost]
public IHttpActionResult PostSavedReportExcel([FromBody]List<SavedReports> savedReports, [FromUri] string dateid)
{
//some excel creation code
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(new MemoryStream(package.GetAsByteArray()))
};
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = dateid + "_report.xlsx"
};
ResponseMessageResult responseMessageResult = ResponseMessage(response);
return responseMessageResult;
}
Usually, for this kind of result I could use window.location = myurltocontroller to download properly , but that is only for GET requests, POST anything is not possible.
I found some answers which could help me in this topic:
JavaScript post request like a form submit
This points out I should go with creating a form, which passes the values, but I do not know how to do so in case of arrays (the table consists 50*10 = 500 values which I have to pass in the form)
I tried some only frontend solutions to the html-excel export problem, which of course does not require to build files on api side, but free jquery add-ins are deprecated, not customizeable, handle only .xls formats, etc.
I found EPPlus nuget package a highly customizeable tool, that is why I want to try this is at first place.
So the question is: how can I post an array of 500 elements, that the controller will recognize, generate the file, and make it automatically download from browser?
If you can provide some code that would be fantastic, but giving me the right direction is also helpful.
Thank you.
You can use fetch() (docs) to send the request from the JS frontend. When the browser (JS) has received the response, it can then offer its binary content as a download. Something like this:
fetch("http://your-api/convert-to-excel", // Send the POST request to the Backend
{
method:"POST",
body: JSON.stringify(
[[1,2],[3,4]] // Here you can put your matrix
)
})
.then(response => response.blob())
.then(blob => {
// Put the response BLOB into a virtual download from JS
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(blob, "my-excel-export.xlsx");
} else {
var a = window.document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "my-excel-export.xlsx";
a.click();
}});
So the JS part of the browser actually first downloads the file behind the scenes, and only when it's done, it's triggering the "download" from the browsers memory into a file on the HD.
This is a quite common scenario with REST APIs that require bearer token authentication.
This is my first post of stackoverflow. I've spent the past month trying to solve this on my own, extensively searching Google and this website. Here's my problem:
I have a website where users can search for cases. When they find a case, their results are loaded on a case details page. Users normally search for cases from the homepage by clicking a search option, where they enter a case number like the following:
14-12345
Users submit their search to the homepage's index controller. The controller parses the search and redirects to a "case" action. This action polls the database to get case details, and returns a case view.
This search works - users see the results of their search on a case details page. However, a request was received so users can search for cases from the case details page as well.
I can't make the new request work. I've tried using Web API (which really became a waste of time, because I want to return a whole view, not just search data), and I've failed to create the appropriate controller/view combination to work with my data. I usually wind up trying to use the existing controller (which has the code to search) and the case details view.
Breaking down the pieces...
The model data is stored in a viewmodel file:
public class PortalCaseView
{
public DocketCase CaseInfo { get; set; }
public List<CaseNote> Notes { get; set; }
public string Search { get; set; }
...other various variable declarations, etc
}
The Index.cshtml file is the homepage/main landing page for the site. Users can search for case details by going to a section to search (code from the view here):
<div class="tile">
<span>Search by Case Number</span>
#Html.EditorFor(x => x.Search)
<a class="m-btn green-stripe" href="javascript:submitForm();" style="color: #444;
text-decoration: none;">Submit<i class="icon-hdd"></i></a> <a class="m-btn red-stripe"
href="javascript:toggleSearch();" style="color: #444; text-decoration: none;">Cancel<i
class="icon-remove"></i></a>
</div>
(Submitting the result gives a submit command, which posts the search to the controller.)
The PortalController.cs controller file directs requests for the homepage. Its Index method grabs the Search variable, and redirects to a Case action to process:
Index
if (!string.IsNullOrWhiteSpace(viewmodel.Search))
{
...
return RedirectToAction("Case", new { Year = docketnumber[0], Sequence = docketnumber[1], J = viewmodel.JudgeCode });
}
Case
[HttpGet]
public ActionResult Case(int Year, int Sequence, string J)
{
...various declarations and requests to get db information...
return View(vm); //vm is a viewmodel with info for the case view
}
[HttpPost]
public ActionResult Case(PortalCaseView vm)
{
return View(vm);
}
* When the redirect to the Case action is complete, the Case.cshtml view loads with the necessary details. Now that searches from the case view are required, I've added a section to the case view to take an "on-demand" search query:
<textarea id="searchForCase" style="width: 150px;"></textarea>
<a class="m-btn green-stripe" href="javascript:searchCase();" style="color: #444;
text-decoration: none;">Search<i class="icon-hdd"></i></a>
And here is where problems start. During a typical run, Ajax/JSON code builds a call back to a controller action. Then, the action executes (usually on data stored in a file or folder). A view is returned, and then the page refreshes. (This is when a user is using the site.) It doesn't work the same way for my new custom code.
My custom searchCase() function takes the case number entered by a user to search for it (it goes back to the Index action in the original PortalController.cs file):
var searchCase = function () {
var textArea = document.getElementById("searchForCase");
var txt = String(textArea.value);
jQuery.ajax({
type: "POST",
url: "#Url.Action("Index","Portal")",
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ fromCaseSearch: txt }),
success: function (data) {
alert(data);
},
failure: function (errMsg) {
alert(errMsg);
}
});
$('form').submit();
}
(Note: I've already tried changing the datatype from json to html; I'm assuming I might need to work with the content type)
I've traced the progress from this point. The website goes back to PortalController.cs and to the Index action (following through like a good POST), and then takes the search query provided by the user. Since it falls out of the Index action when I try to redirect to the Case action like before, I added an if statement to deal with search queries directly from the case details view:
if (!string.IsNullOrEmpty(fromCaseSearch))
{
System.Web.HttpContext.Current.Session.Clear();
//forget why I put that clear request there, but I do
//use session state variables, so I might need to clear
//them when searching from scratch - if they exist
viewmodel = new PortalIndexView();
viewmodel.Search = fromCaseSearch;
...initialization and parsing for search...
...searching for and loading data from database(s)...
ModelState.Clear(); //was hoping this would "refresh" view
//...this didn't work either
return View("Case", vm); //forced website to return
//case view with viewmodel data
//...this actually starts loading
//the case view
}
This actually works... to a point. The data gets processed the same way, and the case view is loaded like normal. However, even after tracing the loading process (and seeing that model variables are sent to the view), the page does not update with the new information).
So, that's where I'm stuck. I've tried tweaking some settings (and even thought the problem might be in the web.config file), but when I run across problems like this, what usually fixes the problem for me is to find out what I did wrong here (or in the general vicinity of the problem - the answers usually happen when I fix simple stuff first).
Some last-minute things:
PortalController.cs (the controller) outputs to Case.cshtml (the case details view). When right-clicking the View controller action, it redirects back to PortalController.cs (the main controller from the landing page).
No other controller shares Case.cshtml.
There is no CaseController.cs file. It looks like my predecessor simply created the search "redirect", figuring users would only search from the homepage (and they did up until now).
Last-second idea? Maybe I'm supposed to account for HTTPGet and HTTPPost actions for my new code as well. Not sure... brain is mush...
Naturally, since this is a work project, I can only provide so many details, but I'll be glad to take suggestions at this point.
UPDATE: is it possible that my problem is because I didn't include a #using (Html.BeginForm(...)) line in my case details view? I noticed there isn't one. Do you need one to be able to have official POST action in the related controller?
One other clarification: I'm using Ajax/JSON for passing data back to the controller from the case view, since I can't get the page to just "submit" (in Javascript/JQuery code, $('form').submit() does not work at all). I'm wondering if it has anything to do with Web API. (There is a Web API file called CaseNoteController.cs that handles notes that get added to cases. When I was trying to work with Web API - and possibly return a view using it - I had a test api call that made the case details view page refresh effortlessly; I just couldn't figure out how to get it to work for my needs.)
Problem solved (my fault). My lack of experience kept me from figuring this out sooner:
I wound up not needing the searchCase function in the Case view. The reason why: I didn't wrap my Case view in a #using(Html.BeginForm()) code block, which meant that my page wasn't posting results (no matter how hard I tried)
I didn't pay attention to my PortalController.cs file, which already had HttpGet and HttpPost variants of my Case action. (The HttpPost action was completely empty, save for returning a view. That explains why the page failed to load and display anything, let alone the results of my next case search.)
Once I corrected those issues (and tweaked a few other buttons so they didn't automatically post or try to run when I submitted the document), the code finally worked! Praise God. No need for convoluted or weird paths back to the controller - things happened real quick after that.
(Now to trim off a whole bunch of scaffolding and otherwise unnecessary code...)
I'm working on an open source OFFLINE pyramid application running on a raspberry pi so I want to keep everything as simple and efficient as possible. The application is happily communicating with my postgresql db using sqlalchemy. I don't want to complicate things by using Deform or other widgets or add authentication or use session cookies (as in the Pyramid Doc examples). My template (chameleon) using the twitter bootstrap contains a form (snippets):
<form action="." method="post">
<a class="btn" href="#" id="actionbtn" onclick="camCapture()">Capture</a>
<p><input type="text" class="input-small' id="projfld" value="Project Name">
<p><label class="control-label" for "select01">Brightness</label>
<div class="btn-group' data-toggle="buttons-radio" id="bright">
<button type="button" class="btn btn-small" id="btnoff">Off</button>
<button type="button" class="btn btn-small" id="btn10">10%</button>
<button type="button" class="btn btn-small" id="btn50">50%</button>
<button type="button" class="btn btn-small" id="btnfu">Full</button>
</div>
<p><label class="checkbox"><input type="checkbox" id="auto">Auto</label>
</form>
There are other buttons and fields in the form, but this is the diversity. When user clicks the actionbtn the javascript in the template:
function camCapture()
{
jQuery.ajax({
url: '/camcapture',
dataType:"json",
type: 'POST',
data: "test",
});
}
is triggering the function in views.py (trimmed code below) and adding system data to a postgresql database using sqlalchemy.
#view_config(renderer="json", name="camcapture")
def camcapture_view(self):
#
# stuff happens
#
now = datetime.datetime.utcnow()
outfile = ("/home/brian/cam/%s" % now.strftime("CAM_%Y%m%d%H%M%S") + ".jpg")
cam_event = Event('fileprefix',now,'CAM',outfile,'memo field content','project')
DBSession.add(cam_event) # data, Event arguments above, are written to database
#
# other stuff happens
#
return []
The user needs only return to the original page (no redirect) as they are likely to alter data in one or more fields and submit the form again.
I reckon this is a common application but I cannot find an example or tutorial to help new developers like me. Perhaps it is too simple and I am missing important fundamental information? My db schema is, I believe appropriately defined in models.py:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
scoped_session,
sessionmaker,
)
from zope.sqlalchemy import ZopeTransactionExtension
engine = create_engine('postgresql://scan:scan#localhost:5432/scan', echo=True) # Establishes SQLAlchemy link to postgresql db
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
class ORMClass(object): # Allows grabs of database values as variables in python
#classmethod
def query(class_):
return DBSession.query(class_)
#classmethod
def get(class_, record):
return Session.query(class_).get(record)
Base = declarative_base(cls=ORMClass)
class Event(Base): #Schema description which allows automated table generation if it doesn't already exist
__tablename__ = 'event'
record = Column(Integer, Sequence('event_record_seq'), primary_key=True, nullable=False)
fileprefix = Column(String)
date = Column(DateTime, nullable=False)
eventtype = Column(String(3))
filename = Column(String)
memo = Column(String)
project = Column(String)
extend_existing = True
def __init__(self, fileprefix, date, eventtype, filename, memo, project): #constructor for new event records
self.fileprefix = fileprefix
self.date = date
self.eventtype = eventtype
self.filename = filename
self.memo = memo
self.project = project
def __repr__(self):
return "<Event('%s','%s','%s','%s','%s','%s')>" % (self.fileprefix, self.date, self.eventtype, self.filename, self.memo, self.project)
Base.metadata.create_all(engine)
Thank you. Yes, #Sergey, I'm having problems accessing the values in the JSON dict. I have gone through that tutorial prior to posting my query. In my attempts to emulate it, I am used stringify to construct the JSON body. I'm taking form information and setting the values into a variable.
jsondata= JSON.stringify({"var1name" : $(#projfld").val(), "var2name" and so on.
On my post--> data: jsondata.
In the tutorial they print the tuples. I'm trying to set each pair as variables for use in the code and can't find the syntax to do that. I want to do something like
var1 = request.json_body(index(0)), var2 = request.json_body(index(1)), and so on.
In the simplified version above I'm sending the string "test" as JSON data. I can then set a string in my Python code to that string, but how do I parse the parts of the JSON body? variablez = request.json_body is the tutorial example equivalent. Since the tuples contain name and value pairs I thought variables might already be declared in the body and I could use them in my code and send some of the values off to my database.
The reason I'm not using a simple submit button is that there are several buttons on the form (mentioned) each of which will take different pieces of information from the form and activate different pieces of code on the server side for immediate hardware action (sending serial commands in this case). Were you trying to suggest that pressing enter in a field will automatically submit the form? Not true, at least not on the system I'm using (which I have control over in this offline application).
It's not really clear what is the exact problem you're having, but I think you don't know how to access values in the JSON dict which the client sends in the POST request body, right?
The answer is request.json_body. Here's a short tutorial in Pyramid docs: Dealing With A JSON-Encoded Request Body. The method is just a thin wrapper which does return json.loads(request.body)
If you send a Javascript Object on the client side (as you seem to be doing with var jsondata = JSON.stringify({var1name: $(#projfld").val(), var2name:...});), on the Python side you'll get a dict. There's no automatic assigning values to any variables, it's just a plain normal dict:
data = request.json_body
event.fileprefix = data['fileprefix']
event.eventtype = data['eventtype']
and so on.
(Another unrelated suggestion: contrary to what Pyramid tutorials suggest, you don't need that unsightly __init__ method, SQLAlchemy generates a default constructor for your model which does exactly the same. Just remove it)
let's say one of my urlpatterns looks like this.
url('^objects/update/(?P<pk>\d+)$', views.UpdateView.as_view(), name = 'update-object'),
I need to redirect user to the update page depending on the selected object (the list of objects is populated using Ajax). So I'd like to pass that named url pattern to the JavaScript, in order to build the actual url on the client side.
Example of what I want to achieve:
pass the name 'update-objects' to the function
get the actual url pattern, replace (?P<pk>..) with {pk}
pass the result to the javascript, resulting in : objects/update/{pk}
any tips?
thanks
to make it more clear: at the moment of rendering, I can't do url reverse because the PK is not known yet. I need to make kind of javascript-urlpattern which will later be converted to the real url (i.e. my JS code will replace {pk} part with the actual pk value)
The actual URL reversing must happen on the server side. There are several ways to do this, and the most elegant of these probably depends on how exactly your script and markup are set up for this. One thing I've done recently is to attach the URL to a logical element using HTML5 data attributes, which are easy to retrieve using jQuery. If you're not using jQuery, I'll leave it up to you to translate to pure JS. You haven't provided any code or specifics for your client-side, so I'm kind of shooting in the dark here, but maybe this will give you the idea:
Django HTML template:
<ul class="object-list">
{% for object in objectList %}
<li data-update-url="{% url update-objects object.pk %}">object.name</li>
{% endfor %}
</ul>
JS:
$('.object-list').on('click', 'li' function () {
var updateUrl = $(this).data('update-url')
...
});
It sounds like you need to make an additional ajax call once the object has actually been selected. Don't try and second guess your url.conf by trying to work out the url on the client side - you'd just be making trouble for yourself later. Wait till you can get a pk, then use django's reverse function to give you your url (doing anything else violates DRY).
How about creating a simple view that returns the url -
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseBadRequest
def get_url(request):
if request.is_ajax() and request.method == 'POST':
obj_id = request.POST['obj_id']
url = reverse('object-update', kwargs{'pk': obj_id})
return HttpResponse(obj_id)
return HttpResponseBadRequest()
Then write a javascript function that gets the url using an ajax call to your new view and then redirects. You'd call this function as soon as the object's been selected. I would suggest using JQuery to do this, pure javascript will require you to write more code, and probably write browser specific code (depending on your target). Also it supports dealing with django's csrf protection (you'll need to implement this for ajax calls if you haven't already).
var redirect = function(obj) {
$.ajax({
url: '/your-get-url-view/',
method: 'post',
data: {'obj_id': obj},
success: function(url){
window.location = url;
}
});
}
I'm afraid I don't know how you're getting from the selected object to the pk (For simplicity I've assumed it's available to the redirect function) - you may have to do some processing in the view to get there.
I haven't tested the above code, but it should give you an idea of what I'm suggesting.
Try this one:
Reverse method for generating Django urls
https://github.com/mlouro/django-js-utils
One more
https://github.com/Dimitri-Gnidash/django-js-utils
If you have a URL that only has one PK field in it, you could resolve it with any number (e.g. 0), then substitute the number as required.
In my scenario my URL had a pk then an upload_id, so I had to replace on the right most instance of a 0, with <upload_id>, which the JS would replace this string occurance as required:
detele_url_upload_id_0 = reverse(f'{APP_NAME}:api_upload_delete', args=[pk, 0])
prefix, suffix = detele_url_upload_id_0.rsplit('0', 1)
context['generic_delete_url'] = prefix + '<upload_id>' + suffix
Then in the JS:
const deleteUrl = genericDeleteUrl.replace('<upload_id>', uploadId)